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.

Community
  • 1
  • 1
Benjamin Gruenbaum
  • 270,886
  • 87
  • 504
  • 504

2 Answers2

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.

Community
  • 1
  • 1
Benjamin Gruenbaum
  • 270,886
  • 87
  • 504
  • 504
  • 5
    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. – Michael Guterl Jun 17 '14 at 16:42
  • 7
    @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. – Benjamin Gruenbaum Jun 17 '14 at 16:46
  • 1
    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! – Michael Guterl Jun 17 '14 at 17:52
  • Based upon this solution, I have made a module: https://github.com/jprichardson/angular-bluebird Thanks! – JP Richardson Aug 13 '14 at 00:23
  • 1
    For anyone looking to use angular-bluebird. Please consider this quote from @JPRichardson at https://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??" – JimTheDev Sep 17 '14 at 13:59
  • 1
    @JimTheDev 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. – Benjamin Gruenbaum Sep 17 '14 at 14:10
  • 1
    I'm trying out https://github.com/mattlewis92/angular-bluebird-promises as a drop in replacement for `$q` and it seems to do a good job so far. – pulkitsinghal Jun 18 '15 at 23:20
  • 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? – scaryguy Aug 01 '15 at 23:46
  • 1
    Thank god for this. I was going mindless because bluebird didn't play well with $http promises. – Guido Tarsia Dec 02 '15 at 15:29
  • 1
    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. http://jsfiddle.net/au1gqkse/3/ – Piero Maltese Mar 22 '16 at 10:34
  • I get a bunch of errors => angular.js:14328 Error: [$rootScope:inprog] $digest already in progress http://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:21 – Alexander Mills Mar 14 '17 at 01:20
  • @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. – Alexander Mills Mar 14 '17 at 01:22
  • @HarryBurns no, you can override Promise.prototype.then but probably shouldn't – Benjamin Gruenbaum Feb 19 '18 at 11:34
  • @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)` – Harry Burns Feb 21 '18 at 01:36
  • @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! – Coffee Sep 08 '21 at 19:52
  • @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. – Harry Burns Sep 17 '21 at 06:58
1

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

Sel
  • 1,934
  • 1
  • 22
  • 13