
I am reviewing other developers' code for a long time by now and saw lots of little mistakes and misunderstandings about howasync
code works in the language.
I think most people learningjavascript
eventually getsPromises
, but there are lots of tiny edge-cases and nitty-gritty details. There is thehigh level understanding of the syntax and semantics of it, but since it's no trivial matter, it requires the"gut instinct" as well to be developed. You need to train yournatural neural network with a long list of examples to develop this ability.
So here it is! An exhaustive list of many different cases (trivial and not so trivial) for people who learnt or are already familiar withPromises
and are looking for nailing them.
How you should use the exercises
First, I will define a very simple function, that creates a new"simulated process", that outputs its ID and its start and finish. Afterwards it returns itsid
, that can be used for later processing.
Copy that into abrowser console
andexecute it.
👀 Thenlook at the example.
🤔 Try toguess in what order will be the logs arriving to the output.
✅ When you made your guess copy-paste the example to the console andrun it.
Found something that confused you? You have found a blind-spot! If you cannot work out what happened, you can ask me in the comments and I will explain! 😊
Note on guessing first
It is important to not justcopy-paste these examples but to guess the results beforehand! The research is quite steady onTest-enhanced learning; so first you work your brain and then see if you were right.
functioncreateProcess(id,timeout){console.log(`🏃️ #${id}: Process started!`);returnnewPromise(resolve=>{functionrunTinyFakeProcess(){resolve(id);console.log(`✅ #${id}: Process finished!`);}setTimeout(runTinyFakeProcess,timeout);});}
Examples
Example 1
Which process will finish first? Which will be the last?
Look for the ✅!
createProcess(1,3000);createProcess(2,2000);createProcess(3,1000);
Example 2
createProcess(1,3000);awaitcreateProcess(2,2000);createProcess(3,1000);
Example 3
awaitcreateProcess(1,3000);awaitcreateProcess(2,2000);awaitcreateProcess(3,1000);
Example 4
awaitcreateProcess(1,3000).then(()=>{createProcess(2,2000);createProcess(3,1000);});
Example 5
awaitcreateProcess(1,3000).then(()=>createProcess(2,2000)).then(()=>createProcess(3,1000));});
Example 6
awaitcreateProcess(1,3000).then(()=>{createProcess(2,2000);}).then(()=>createProcess(3,1000));
Confused about the result and can't see the difference? Check for the{}
around the second call!
Example 7
awaitPromise.all([createProcess(1,3000),createProcess(2,2000),createProcess(3,1000)]);
Example 8
awaitPromise.all([createProcess(1,3000).then(()=>createProcess(2,2000)),createProcess(3,1000)]);
Example 9
awaitPromise.all([awaitcreateProcess(1,3000),awaitcreateProcess(2,2000),awaitcreateProcess(3,1000)]);
Example 9b
Got confused about example #9? Let's break the previous one down:
// But how??? Think about initialization!constprocesses1=[awaitcreateProcess(1,3000),awaitcreateProcess(2,2000),awaitcreateProcess(3,1000)];console.log('Are the processes already done?');// At this point all 3 processes will already be awaited// and this call will immediately return the already// processed results.awaitPromise.all(processes1);
Example 10
Let's do something with the returned values!
constprocesses2=[createProcess(1,3000),createProcess(2,2000),createProcess(3,1000)];awaitPromise.all(processes2.map((item)=>item*2));
Example 11
How did we getNaN
s? 🤔 And how do get the proper answer?
Look!
constprocesses3=[createProcess(1,3000),createProcess(2,2000),createProcess(3,1000)];// first we wait for the processes to finish(awaitPromise.all(processes3))// then we just toy with the results.map(result=>result*2));
Example 11b
Could we have done just this?
constprocesses4=[createProcess(1,3000),createProcess(2,2000),createProcess(3,1000)];awaitPromise.all(processes4.map(async(promise)=>{constresult=awaitpromise;returnresult*2;}));
Actually, yes, we could! But check for the sublety!
In#11
, we waited for all promises to first run to completion and then we processed the result.
In#11b
we are immediately acting upon the promises: we await them, process their result. ThenPromise.all
will wait for the new promises defined by theasync
.map
function which in turn would yield the processed results.
Example 11c
Does creating processes and awaiting them in a.map()
would make them run in series instead of in parallel? 🤔
constvalues=[{id:1,time:3000},{id:2,time:2000},{id:3,time:1000}];// try it!awaitPromise.all(values.map(async([id,time])=>awaitcreateProcess(id,time)));
No, don't worry! During runtime whenawait
is encountered, the "processing" of the code block will be suspended and another block can be processed. In other words, aftercreateProcess
started processing at the moment ofawait
, we switch to another iteration of.map
, which will in turn start another process.
Example 12
Let's check onPromise.race!
awaitPromise.race([createProcess(1,2000),createProcess(2,1000)]);
Who would "win"?
Note
Pay attention to the logs as well!
See anything strange?
Even though#2
was the first and the result of#1
is ignored,#1
also ran to completion! In case process#1
must stop when process#2
"won" the race, then you need
something like acancellable promise! (There are other ways... You can do your own research by searching for"Why can't you cancel promises?").
Example 13
awaitPromise.race([createProcess(1,2000).then(()=>createProcess(3,3000)),createProcess(2,1000)]);
See my previous point? The entire chain ran (defined in the first item in the array), even though process#2
already "won" the race.
Example 14
What will be the last message? For this exercise I am going to pass in some arrays for IDs (I know it's not clean code), becausecreateProcess
merely returns the first parameters.
Therefore this time we can use the awaited return values to start and wait for even more processes.
So what this really should do is waiting for some array data and then run processes for each item in each arrays.
And after everything ran we would like to have have a success message. But did we succeed? 🧐
constprocesses5=[createProcess([1,2,3],1000),createProcess([4,5,6],2000),createProcess([7,8,9],3000)];functionrunPromiseAll1(){returnPromise.all(processes5.map(asyncprocess=>{constids=awaitprocess;awaitPromise.all(ids.map((id,index)=>{createProcess(`${id}->${index}`,Math.random()*5000)}));}));}awaitrunPromiseAll1();console.log('👆 Must be the last message! We waited for everything!');
Is it though?
14b - making order
Let's see if we can make that lastconsole.log
fired at the right time:
functionrunPromiseAll2(){returnPromise.all(processes5.map(asyncprocess=>{constids=awaitprocess;returnawaitPromise.all(ids.map(async(id,index)=>{returnawaitcreateProcess(`${id}->${index}`,Math.random()*5000);}));}));}awaitrunPromiseAll2();console.log('👆 This _really_ should be the last message after we waited for everything!');
14c: theflatMap
version
And now let's see a"smart" version:
functionrunPromiseAll3(){returnPromise.all(processes5.flatMap(asyncprocess=>awaitprocess).map((id,index)=>createProcess(`${id}->${index}`,Math.random()*5000)));}awaitrunPromiseAll3();console.log('👆 Finished!');
Wait... What? The problem is thatasync
code always wraps the result in aPromise
, so even you return an array it will bealways aPromise
.
14d: the"Let's unpack the array first" version
Let me confess that I needed to try out a couple of things before I could fix this. Async programming combined with arrays in JS is not an easy task.
asyncfunctionrunPromiseAll4(){// we unpack the array from the Promiseconstids=awaitPromise.all(processes5);// and now we can use whatever arrayconstnewPromises=ids.flatMap(id=>id).map(item=>{console.log(item);returnitem;}).map((id,index)=>createProcess(`${id}->${index}`,Math.random()*5000));returnPromise.all(newPromises);}awaitrunPromiseAll4();console.log('😅 Finished!');
Summary
If you find async programming hard, you are right! I think most of us gets the simple notion of some code blocks are running in parallel, however using a simple document to express it is a harder task. There are no visual aids on which blocks would run to completion, which can run in parallel. You have to really-really pay attention to the fine details of the code.
And I think it is a language design problem as well. Until we find a better to express it, we should hone the sharpness of our wits and eyes!
Top comments(1)

- LocationBudapest
- EducationEötvös Loránd University (ELTE - Budapest Hungary) Computer Science M. Sc.
- WorkSenior Full-stack and React-Native Engineer
- Joined
After seeing some real cases in the "field" I have added a couple of more exercises. (Ex. 14)
For further actions, you may consider blocking this person and/orreporting abuse