Await in turn by mistake
I really enjoy working with the new async functions but it is really easy to set up a situation where code that could be running in parallel is force to run in sequence. Consider this simple invocation of a javascript function that takes two values returned from other async functions:
const combination = await combine(await value(1), await value(2));
The problem here is that unless the JS environment does some optimisation for you, the actions contained in the two called to await are performed in series.
function action() { return new Promise(resolve => { setTimeout(resolve, 5000) }) } async function value(v) { console.log('started ' + v); return action('broken ' + v).then(()=> { console.log("finished " + v); return "ok " + v }) } async function test() { console.log(await value('1') + " " + await value('2')); } test();
This will result in an output that look something like this, and will take about ten seconds:
(index):39 started 1 (index):39 finished 1 (index):37 started 2 (index):39 finished 2 (index):44 ok 1 ok 2
So there are a number of way to re-write this code in order to ensure that the original processes are all started at the same time, I think the second is the best form, just wish they had put some syntactic sugar so we could do away with Promise.all references.
async function test() { // Worst const three = value('3'); const four = value('4'); console.log(await three + " " + await four); // Better const [five, six] = await Promise.all([value('5'), value('6')]; console.log(five + " " + six)); // Better? console.log(...await Promise.all([value('7'), value('8')])) } test();
So you would expect a test output to be similar to this, with each block taking about the minimum 5 seconds.
(index):37 started 3 (index):37 started 4 (index):39 finished 3 (index):39 finished 4 (index):47 ok 3 ok 4 (index):37 started 5 (index):37 started 6 (index):39 finished 5 (index):39 finished 6 (index):48 ok 5 ok 6 (index):37 started 7 (index):37 started 8 (index):39 finished 7 (index):39 finished 8 (index):48 ok 7 ok 8
It is really easy to make the same mistake when writing a for loop for example by waiting on each item in turn
async function test() { const list = ['1', '2', '3']; const result = []; for (const item of list) { result.push(await value(item)); } console.log(result); }
For most operation that involve Promise and loop you will normally need map/Promise.all at some point. This version should complete in around the minimum 5 seconds.
async function test() { const list = ['1', '2', '3']; const result = await Promise.all(list.map(value)); console.log(result); }
Published on Web Code Geeks with permission by Gerard Davison, partner at our WCG program. See the original article here: await in turn by mistake Opinions expressed by Web Code Geeks contributors are their own. |