How do I use Bluebird with Angular?
Asked Answered
H

2

45

I tried using Angular with Bluebird promises:

HTML:

<body ng-app="HelloApp">
    <div ng-controller="HomeController">{{name}} {{also}}</div>
</body>

JS:

// javascript
var app = angular.module('HelloApp', []);

app.controller("HomeController", function ($scope) {
    var p = Promise.delay(1000).then(function () {
        $scope.name = "Bluebird!";
        console.log("Here!", $scope.name);
    }).then(function () {
        $scope.also = "Promises";
    });
    $scope.name = "$q";
    $scope.also = "promises";
});

window.app = app;

[Fiddle]

However, no matter what I tried, it kept staying "$q promises" and did not update. Except if I added a manual $scope.$apply which I'd rather avoid.

How do I get Bluebird to work with AngularJS?

(I know it's possible since $q does it)

I'm using Bluebird 2.0 which I got here.

Holeandcorner answered 1/6, 2014 at 21:6 Comment(0)
H
61

This is possible, and even quite easy!

Well, if we look at how Angular's own promises work, we need to get Bluebird to $evalAsync somewhere in order to get the exact same behavior.

If we do that, the fact both implementations are Promises/A+ compliant means we can interop between $q code and Bluebird code, meaning we can use all of Bluebird's features in Angular code freely.

Bluebird exposes this functionality, with its Promise.setScheduler functionality:

// after this, all promises will cause digests like $q promises.
function trackDigests(app) {
    app.run(["$rootScope",function ($rootScope) {
        Promise.setScheduler(function (cb) {
            $rootScope.$evalAsync(cb);
        });
    }]);
}

Now all we have to do is add a:

trackDigests(app); 

line after the var app = ... line, and everything will work as expected. For bonus points, put Bluebird in a service so you can inject it rather than use it on the global namespace.

Here is a [Fiddle] illustrating this behavior.

Note that besides all the features Bluebird has over $q, one of the more important ones is that Bluebird will not run $exceptionHandler, but instead will automatically track unhandled rejections, so you can throw freely with Bluebird promises and Bluebird will figure them out. Moreover calling Promise.longStackTraces() can help with debugging a lot.

Holeandcorner answered 1/6, 2014 at 21:6 Comment(16)
Is there any reason NOT to use Bluebird with Angular in this way? I'm ready to start benefiting from Bluebird's API, but I don't want to get bitten later on because of some unanticipated consequence.Aberdare
@MichaelGuterl That's a great question. Yes, there are. It introduces another dependency (maintainers have to understand one more thing - and might even rely on broken semantics like throw always logging). Second, the bigger builds of it are about 12kb which is not insignificant. Third, $q is not run against the Bluebird test suite (and vice versa) but rather the two are both tested against a subset (Promises/A+) so a $q library bug might be hard to find in Bluebird. IMO these are fairly minor, I've been very successful mixing the two and would definitely recommend it. The gain is huge.Holeandcorner
based on your feedback I think there's very little reason for us not to use bluebird. I'm going to add it to our project today. Thanks!Aberdare
Based upon this solution, I have made a module: github.com/jprichardson/angular-bluebird Thanks!Titer
For anyone looking to use angular-bluebird. Please consider this quote from @JPRichardson at github.com/jprichardson/angular-bluebird/issues/1 "I'm starting to believe this library is actually a very bad idea. Promise.setScheduler (Bluebird) with Angular's $digest cycle has a lot of bad repercussions. Especially when you want to use Bluebird promises in non-ui code... like a database wrapper. Maybe it's just best to use Angular's $q for UI related code and Bluebird promises elsewhere??"Favela
@Favela I have not experienced this to be the case in several large scale applications. Of course scheduling a digest on promises resolving makes the assumption you're usually resolving promises with the eventual purpose of updating the UI - if that's not the case you have the same problem with $q anyway.Holeandcorner
I'm trying out github.com/mattlewis92/angular-bluebird-promises as a drop in replacement for $q and it seems to do a good job so far.Strangury
I'm using Yeoman angular fullstack and I couldn't implement your solution to use bluebird as a replacement for $q ... Could you please add some details to your answer regarding things to do with Yeoman angular fullstack?Clarence
Thank god for this. I was going mindless because bluebird didn't play well with $http promises.Ekaterinoslav
A quick tip for solving the issue with $exceptionHandler, that for me is the biggest gotcha. Bluebird will emit a global "unhandledrejection" event on the window object. jsfiddle.net/au1gqkse/3Galba
I get a bunch of errors => angular.js:14328 Error: [$rootScope:inprog] $digest already in progress errors.angularjs.org/1.6.1/$rootScope/inprog?p0=%24digest at angular.js:68 at beginPhase (angular.js:18335) at Scope.$digest (angular.js:17763) at Scope.$apply (angular.js:18080) at home.controller.js:21Mumble
@BenjaminGruenbaum this answer might be a bit dated (not your fault)...these days I think most people would find it to be perfectly acceptable to put Bluebird's Promise in the global namespace and have it override the ES6 Promise val.Mumble
@HarryBurns no, you can override Promise.prototype.then but probably shouldn'tHoleandcorner
@BenjaminGruenbaum, thanks! Just checked. Created a subclass and made $applyAsync() inside. Digest cycle began to be called 20 times more often and everything began to slow down. So I just wrapped my native promises with $q.when(otherPromise)Washburn
@HarryBurns I realize this is a bit dated but could you please elaborate on what you mean/did? I'd hope my application wouldn't slow down let alone gaining speed :) Thanks!Babiche
@Coffee, as I said before, I just used $q.when(otherPromise) to make the angular wrap my promises in its own. Before that, I tried to replace Promise.prototype.then using $applyAsync() inside, but nothing good came of it, and it never will. So just wrap your promises in $q.when and enjoy.Washburn
U
1

Library Angular bluebird promises replaces $q service with bluebird. $http also run through bluebird

Unpin answered 17/11, 2016 at 12:52 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.