I spent a little time in a Promise rabbit-hole today, and thought I'd share two rules-of-thumb that I came up with to avoid such holes in the future. This came from writing Mocha tests, but it applies to lots of code:
Example:
describe('Some Async Test', () => {
var myPromise;
before('Do some async setup', () => {
myPromise = doSomethingAsync();
return myPromise;
});
it('Should do something else async', () => {
return myPromise.then( () => {
doSomethingElseAsync() //no return!!
.then((response) => {
//more processing
});
});
});
});
The only thing critically wrong with this is that the Promise returned by doSomethingElseAsync
is not returned. Thus, mocha will think that the test has completed (since the test function returns a Promise that resolves to undefined instead of a Promise that resolves to another Promise).
To avoid this, we can do two things:
Always try to keep promise chains one-dimensional. Nesting .then
inside of functions is a sure-fire way to get mixed up. Here is the above example with just this rule applied to it:
describe('Some Async Test', () => {
var myPromise;
before('Do some async setup', () => {
myPromise = doSomethingAsync();
return myPromise;
});
it('Should do something else async', () => {
return myPromise
.then(doSomethingElseAsync)
.then((response) => {
//more processing
});
});
});
Remember that if a mocha it
, before
, beforeEach
, etc... is passed an expression that returns a promise (as these are), then mocha will wait for that promise to resolve before continuing. Consequently, we don't need to chain onto myPromise
in the tests at all.
Here's what this looks like applying these rules of thumb:
describe('Some Async Test', () => {
before('Do some async setup', () => {
return doSomethingAsync();
});
it('Should do something else async', () => {
return doSomethingElseAsync()
.then((response) => {
//more processing
});
});
});
});
tip: You can use lodash's _.partial
and _.partialRight
function if you need to pass extra arguments to the function that you are putting in .then
:
function handleBothArgs(arg1, arg2) {
//...
};
function run() {
var arg2 = 'foo';
return getArg1Async()
.then(_.partialRight(handleBothArgs, arg2)); //will call handleBothArgs(arg1, arg2) :-)