Movatterモバイル変換


[0]ホーム

URL:


Skip to content
DEV Community
Log in Create account

DEV Community

Cover image for Are you sure you know Promises?
András Tóth
András Tóth

Posted on • Edited on

     

Are you sure you know Promises?

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 learningjavascripteventually 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);});}
Enter fullscreen modeExit fullscreen mode

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);
Enter fullscreen modeExit fullscreen mode

Example 2

createProcess(1,3000);awaitcreateProcess(2,2000);createProcess(3,1000);
Enter fullscreen modeExit fullscreen mode

Example 3

awaitcreateProcess(1,3000);awaitcreateProcess(2,2000);awaitcreateProcess(3,1000);
Enter fullscreen modeExit fullscreen mode

Example 4

awaitcreateProcess(1,3000).then(()=>{createProcess(2,2000);createProcess(3,1000);});
Enter fullscreen modeExit fullscreen mode

Example 5

awaitcreateProcess(1,3000).then(()=>createProcess(2,2000)).then(()=>createProcess(3,1000));});
Enter fullscreen modeExit fullscreen mode

Example 6

awaitcreateProcess(1,3000).then(()=>{createProcess(2,2000);}).then(()=>createProcess(3,1000));
Enter fullscreen modeExit fullscreen mode

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)]);
Enter fullscreen modeExit fullscreen mode

Example 8

awaitPromise.all([createProcess(1,3000).then(()=>createProcess(2,2000)),createProcess(3,1000)]);
Enter fullscreen modeExit fullscreen mode

Example 9

awaitPromise.all([awaitcreateProcess(1,3000),awaitcreateProcess(2,2000),awaitcreateProcess(3,1000)]);
Enter fullscreen modeExit fullscreen mode

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);
Enter fullscreen modeExit fullscreen mode

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));
Enter fullscreen modeExit fullscreen mode

Example 11

How did we getNaNs? 🤔 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));
Enter fullscreen modeExit fullscreen mode

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;}));
Enter fullscreen modeExit fullscreen mode

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)));
Enter fullscreen modeExit fullscreen mode

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)]);
Enter fullscreen modeExit fullscreen mode

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)]);
Enter fullscreen modeExit fullscreen mode

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!');
Enter fullscreen modeExit fullscreen mode

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!');
Enter fullscreen modeExit fullscreen mode

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!');
Enter fullscreen modeExit fullscreen mode

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!');
Enter fullscreen modeExit fullscreen mode

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)

Subscribe
pic
Create template

Templates let you quickly answer FAQs or store snippets for re-use.

Dismiss
CollapseExpand
 
latobibor profile image
András Tóth
A developer with M.Sc. in Computer Science. Working professionally since 2010. In my free time I make music and cook.Also I don't and after the recent events will not have Twitter.
  • Location
    Budapest
  • Education
    Eötvös Loránd University (ELTE - Budapest Hungary) Computer Science M. Sc.
  • Work
    Senior 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)

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment'spermalink.

For further actions, you may consider blocking this person and/orreporting abuse

A developer with M.Sc. in Computer Science. Working professionally since 2010. In my free time I make music and cook.Also I don't and after the recent events will not have Twitter.
  • Location
    Budapest
  • Education
    Eötvös Loránd University (ELTE - Budapest Hungary) Computer Science M. Sc.
  • Work
    Senior Full-stack and React-Native Engineer
  • Joined

More fromAndrás Tóth

DEV Community

We're a place where coders share, stay up-to-date and grow their careers.

Log in Create account

[8]ページ先頭

©2009-2025 Movatter.jp