Asynchronous Exception Handling with Bluebird Promises

Asynchronous exception handling with bluebird promises

Promises are not domains, they will not catch exceptions from asynchronous callbacks. You just can't do that.

Promises do however catch exceptions that are thrown from within a then / catch / Promise constructor callback. So use

function getPromise(){
return new Promise(function(done, reject){
setTimeout(done, 500);
}).then(function() {
console.log("hihihihi");
throw new Error("Oh no!");
});
}

(or just Promise.delay) to get the desired behaviour. Never throw in custom (non-promise) async callbacks, always reject the surrounding promise. Use try-catch if it really needs to be.

Why is an error thrown from within an async function inside a bluebird promise not caught in the .catch() function?

Because the exception occurs inside the fs.open() async callback so that exception goes back into the async event handler in fs.open() that called the completion callback where it then disappears and has no chance to be propagated anywhere. Bluebird never has a chance to see it.

This is a textbook example of why you should not mix regular async callback code with promise code. Instead, promisify fs.open() and use the promisified version and then the exception will be caught appropriately by the promise infrastructure.

fs.openAsync = function(fname, mode) {
return new Promise(function(resolve, reject) {
fs.open(fname, mode, function(err, fd) {
if (err) return reject(err);
resolve(fd);
});
});
}

function bar() {
return fs.openAsync('myfile', 'r').then(function(fd) {
baz(); // throws TypeError [].toDateString which will now be caught
// in the .catch() below
}).catch(function(err) {
// handle error here
});
}

Or, in Bluebird, you can use the built-in promisify functions:

const fs = Promise.promisifyAll(require('fs'));

That will automatically create fs.openAsync() which returns a promise and promisified versions of all the other async methods.


FYI, the promise infrastructure can only catch exceptions in callbacks that are called by the promise infrastructure itself. It does that by wrapping the call to each callback in it's own try/catch. As you see in your code, you are using the fs.open() callback directly which has no such chance to get wrapped in such a try/catch handler to catch the exception and turn it into a rejection. So, instead, the usual solution is to create a promisified version of fs.open() that immediately rejects or resolves in the callback and then your custom callback code goes in the .then() handler where the callback is wrapped and exceptions will be caught and automatically turned into rejections.

Bluebird Promises: sequential asynchronous execution of chained promises with for-loops inside

You do not return the Promise created by promiseFor. As of that, the chain is broken, and the .then(function(result2) { does not wait for that code to be finished. You need to add a return in front of the promiseFor(function(count) {

  .then(function(result1) {

var promiseFor = Promise.method(function(condition, action, value) {
if (!condition(value)) return value;
return action(value).then(promiseFor.bind(null, condition, action));
});

return promiseFor(function(count) {
return count < result1.length;
}, function(count) {
return Object.someOtherAsyncAction(someParam)
.then(function(res) {
return ++count;
});
}, 0)

})

Bluebird promises on Node.js: error and exception handling

.promisify*() turns the regular Node.js callback convention into promises. Part of that convention is that errors are passed as first argument to a callback function.

In other words, to reject the promise, use cb(new Error(...)).

Example:

var Promise = require('bluebird');

var library1 = {
// If `doFail` is true, "return" an error.
doSomething : function(doFail, cb) {
if (doFail) {
return cb(new Error('I failed'));
} else {
return cb(null, 'hello world');
}
}
};

var doSomething = Promise.promisify(library1.doSomething);

// Call that resolves.
doSomething(false).then(function(result) {
console.log('result:', result);
}).catch(function(err) {
console.log('error:', err);
});

// Call that rejects.
doSomething(true).then(function(result) {
console.log('result:', result);
}).catch(function(err) {
console.log('error:', err);
});

As for the missing error types: I assume that they are exported by secondlibrary so use library2.LinkError instead of just LinkError. If they aren't exported you cannot catch them explicitly.

Handling multiple nested async operations in Bluebird (promises)

Can it be improved?

Some things you can improve since you are using Bluebird.

Use Bluebird's promise.map() to save code:

Instead of array.map() followed by promise.all(), you can just use Bluebird's promise.map() by changing code like this:

               var childItems = data2.Items;

var morePromises = childItems.map(function(device) {
return doAsyncWork()
.then(function success() {
console.log("Success!");
})
.catch(function error(err) {
console.log("Error!");
})
});

return promise.all(morePromises);

to this:

               return promise.map(data2.Items, function(item) {
// do something with item
return doAsyncWork();
});

Be careful about .catch() handlers that just log.

If you handle a promise rejection and don't rethrow or return a rejected promise, the promise state will change from rejected to fulfilled.

So, when you use a .catch() like you have in here:

        return dynamodb.queryAsync(params)
.then(function(data2) {
var childItems = data2.Items;

var morePromises = childItems.map(function(device) {
return doAsyncWork()
.then(function success() {
console.log("Success!");
})
.catch(function error(err) {
console.log("Error!");
})
});

return promise.all(morePromises);
})

That will "eat" any error that comes from a rejected promise from doAsyncWork(). Occasionally that's what you want (you want to handle the error and continue as if nothing went wrong), but many times you need to error to get propagated back somehow. You can log it, but let the error propagate by rethrowing it:

        return dynamodb.queryAsync(params)
.then(function(data2) {
var childItems = data2.Items;

var morePromises = childItems.map(function(device) {
return doAsyncWork()
.then(function success() {
console.log("Success!");
})
.catch(function error(err) {
console.log("doAsyncWork Error: ", err);
//rethrow so error propagates
throw err;
})
});

return promise.all(morePromises);
})

For all async operations of an item to complete I'm using
Promise.all(), and from the documentation I can see that if even one
promise got rejected the subsequent promises will get rejected as
well. I don't want this to happen, I want it to continue even if a
single promise is rejected.

In Bluebird, if you don't want a Promise.all() to abort when one promise rejects, you can use Promise.settle() instead of Promise.all() if you're using Bluebird 2.x. If you're using Bluebird 3.x, then you use .reflect() when you're returning your promises. How to do this is explained here in the Bluebirds docs. Personally, I liked the way Promise.settle() works, but there must have been some standards-direction reason for changing it.

There is a lot of nesting, how may I improve upon this code?

You can chain some of what you're doing instead of nesting. See How to chain and share prior results with Promises for a variety of ways to sequence multiple operations without so much nesting and accumulate results.

Get Bluebird Promise from async await functions

Is there a way to change the default Promise returned by AsyncFunction

No.

What are the reasons of this locking

The ability to hijack all async functions could be a security issue. Also, even where that is no problem, it's still not useful to do this replacement globally. It would affect your entire realm, including all libraries that you are using. They might rely on using native promises. And you cannot use two different promise libraries, although they might be required.

I want to be able to do something like:

getResolvedAsyncAwaitPromise().tap(...)

What you can do is to wrap the function at its definition with Promise.method:

const Bluebird = require('Bluebird');
const getResolvedAsyncAwaitPromise = Bluebird.method(async () => 'value');

getResolvedAsyncAwaitPromise()
.tap(…) // Works!
.then(…);


Related Topics



Leave a reply



Submit