Porting popular blog posts to til
Originally posted October 24, 2012
I use Q everywhere. Here's a few things that I wish I had known before using promises at work in a production environment (nodejs).
Don't call anything until you're in a promise chain.
Start everything with Q.fcall().
This way exceptions thrown by the first function will be treated as rejected promises, rather than uncaught errors that will blow your server up. Instead of starting your promise chain by calling a function, start with a resolved promise so every function in your chain is treated like a promise callback (converting values into resolved promises, converting thrown exceptions into rejected ones), and most importantly, propegating every exception to your fail handlers.
Q.fcall(firstFunction).then(secondFunction);
// you better be pretty confident firstFunction
// cannot throw an exception to write the following
//
// if you're working on a team, you'll just have to hope no one
// does something as well intentioned as adding parameter validation
// to that function...
firstFunction().then(secondFunction);
Pleasant Side-Effect
Because promises convert returned values into promises resolved with that value, and convert thrown exceptions into promises rejected with that error, you gain some flexibility by ensuring that every function will be treated like a promise callback. For instance, the following poorly written function could never start your promise chain, but does just fine as a callback:
var divideByTwo = function (a) {
if (typeof a !== 'Number') {
throw new Error('NaN');
}
if (!a) {
return NaN;
}
return Q.resolve(a / 2)
};
There is all kinds of return value nonsense in there. But if you started with Q.fcall, no problem!
Trailing exception handler
.then(onSuccess).fail(onError) not .then(onSuccess, onError)
Same issue as above, but on the tail end (and instead of blowing your server up, they'll silently disappear).
Exceptions thrown by onSuccess will not be handled by the onError handler of the same promise. If your onSuccess handler could possibly throw an exception, you need to have another .fail handler outside of it to deal with those.
Example: If you're dealing with http requests, your success handler might check the result object for the status code, and assert that it is 200. You'll need a trailing .fail handler for that. The fail handler for the request itself will only be called if the server is not reached.
Accidentally swallowing exceptions
Its always tempting to introduce .fail handlers throughout your code to get a better idea of specifically where things are going wrong. I'm constantly writing functions that have fail handlers to get better visibility into where errors are coming from. But its important that your logging doesn't inadvertantly result in a resolved promise like below.
That .fail hander doesn't re-throw the error, and doesn't return a promise, so the implicit return; is treated like a resolved promise. So doTask swallows all of its own errors. Make sure your teammates and your future self don't mind being blindsided by errors if you add code like this.
Intentionally throwing exceptions
.done vs .then
Using .done, rather than .then means that if there's a rejected promise that is never handled via a promise failure handler, the promise chain will throw the exception. This means that you'll never silently swallow exceptions (good!), but it also means that there's an exception floating around, likely either crashing your process or leaving it in an unstable state (bad!).
If you are using .done, make sure you're intentionally "failing fast". If you're using .done so you never silently swallow exceptions, try the approaches above instead!
Thoughts?
Know of improvements, or a better way altogether? Have you run into your own issues with promises? Let me know!
I use Q everywhere. Here's a few things that I wish I had known before using promises at work in a production environment (nodejs).
Don't call anything until you're in a promise chain.
Start everything with
Q.fcall()
.This way exceptions thrown by the first function will be treated as rejected promises, rather than uncaught errors that will blow your server up. Instead of starting your promise chain by calling a function, start with a resolved promise so every function in your chain is treated like a promise callback (converting values into resolved promises, converting thrown exceptions into rejected ones), and most importantly, propegating every exception to your fail handlers.
Pleasant Side-Effect
Because promises convert returned values into promises resolved with that value, and convert thrown exceptions into promises rejected with that error, you gain some flexibility by ensuring that every function will be treated like a promise callback. For instance, the following poorly written function could never start your promise chain, but does just fine as a callback:
There is all kinds of return value nonsense in there. But if you started with
Q.fcall
, no problem!Trailing exception handler
.then(onSuccess).fail(onError)
not.then(onSuccess, onError)
Same issue as above, but on the tail end (and instead of blowing your server up, they'll silently disappear).
Exceptions thrown by
onSuccess
will not be handled by theonError
handler of the same promise. If youronSuccess
handler could possibly throw an exception, you need to have another.fail
handler outside of it to deal with those.Example: If you're dealing with http requests, your success handler might check the result object for the status code, and assert that it is 200. You'll need a trailing
.fail
handler for that. The fail handler for the request itself will only be called if the server is not reached.Accidentally swallowing exceptions
Its always tempting to introduce
.fail
handlers throughout your code to get a better idea of specifically where things are going wrong. I'm constantly writing functions that have fail handlers to get better visibility into where errors are coming from. But its important that your logging doesn't inadvertantly result in a resolved promise like below.That
.fail
hander doesn't re-throw the error, and doesn't return a promise, so the implicitreturn;
is treated like a resolved promise. SodoTask
swallows all of its own errors. Make sure your teammates and your future self don't mind being blindsided by errors if you add code like this.Intentionally throwing exceptions
.done
vs.then
Using
.done
, rather than.then
means that if there's a rejected promise that is never handled via a promise failure handler, the promise chain will throw the exception. This means that you'll never silently swallow exceptions (good!), but it also means that there's an exception floating around, likely either crashing your process or leaving it in an unstable state (bad!).If you are using
.done
, make sure you're intentionally "failing fast". If you're using.done
so you never silently swallow exceptions, try the approaches above instead!Thoughts?
Know of improvements, or a better way altogether? Have you run into your own issues with promises? Let me know!