Promise.all with a timeout

Perry Mitchell / 2016-06-23 21:32:31
Promise.all with a timeout

JavaScript’s Promises are a pow­er­ful de­ferred-re­sult pro­cess­ing tool that have changed the way de­vel­op­ers tackle asyn­chronic­ity in to­day’s ap­pli­ca­tions. The stan­dard Promise im­ple­men­ta­tion in browsers and NodeJS uses a few sim­ple fea­tures to rev­o­lu­tionise how asyn­chro­nous meth­ods are called and their re­sults han­dled.

Take, for in­stance, a very com­mon ex­am­ple of why Promises are so cool:

// Using callbacks:
doThisFirst(function(err, data1) {
    if (err) {
        // handle error
    }
    doThisSecond(function(err, data2) {
        if (err) {
            // handle error
        }
        doThisThird(function(err, data3) {
            if (err) {
                // handle error
            }
        });
    });
});

// Using promises:
doThisFirst()
    .then(doThisSecond)
    .then(doThisThird)
    .catch(function(err) {
        // handle error
    });

By now every­one should have seen ex­am­ples sim­i­lar to this be­ing thrown around. A fea­ture of Promises that is slightly less thrashed in ex­am­ples is Promise.all(), which takes an ar­ray of Promises and re­solves with an ar­ray of their re­sults or throws a .catch-able ex­cep­tion:

Promise
    .all([
        Promise.resolve(1),
        Promise.resolve(2),
        Promise.resolve(3)
    ])
    .then(res => res.join(", "))
    .then(str => { console.log(str); }); // 1, 2, 3

Promise.all() makes fetch­ing data from mul­ti­ple re­motes at the same time a breeze, and what you get back is an ar­ray of all the data you need for your ap­pli­ca­tion - but what if you did­n’t want to wait for each end­point to re­spond, and in­stead did­n’t care if all the data came back or not?

Say you had a spec­i­fied amount of time you needed to wait, and no more, for all re­sponses to re­turn. .all comes close to help­ing here, but it pro­vides no na­tive way of spec­i­fy­ing a time­out (as Promises don’t work that way).

With some small amount of wrap­per code, we can im­i­tate Promise.all() and pro­vide a way to spec­ify a max­i­mum time in mil­lisec­onds for the Promises to re­solve:

This promiseAllTimeout method takes an ar­ray just like Promise.all, but it ac­cepts 2 more pa­ra­me­ters:

  • A max­i­mum wait time in mil­lisec­onds. This is ap­prox­i­mate as time­outs in JavaScript are rarely spot-on.
  • A flag to spec­ify the be­hav­iour. If set to true (default), the func­tion will re­solve when the time ex­pires even if not all Promises have re­solved by that time - in this case, un­re­solved Promises will map to undefined. If set to false, if any Promises did not re­solve by the time spec­i­fied an ex­cep­tion is thrown.

Let’s take a look at how it func­tions:

console.time("Promise-all");
promiseAllTimeout([
    new Promise(function(resolve) {
        setTimeout(function() {
            (resolve)(5)
        }, 150);
    }),
    new Promise(function(resolve) {
        setTimeout(function() {
            (resolve)(10)
        }, 280);
    })
], 250, true)
.then(function(results) {
    console.timeEnd("Promise-all");
    console.log("Result:", results);
}).catch(function(err) {
    setTimeout(function() {
        throw err;
    });
});
// Outputs:
//   Promise-all: 255.802ms
//   Result: [ 5, undefined ]

Changing the true in the func­tion call to false would in­stead throw an ex­cep­tion.

This is def­i­nitely not a suit­able re­place­ment for the built-in method, but it does pro­vide a handy in­ter­face for work­ing with time-sen­si­tive com­po­nents.