172

You Don't Know JS: Async & Performance

  • Upload
    others

  • View
    6

  • Download
    1

Embed Size (px)

Citation preview

Page 1: You Don't Know JS: Async & Performance
Page 2: You Don't Know JS: Async & Performance

1. Introduction2. Preface3. Foreword4. TableofContent5. Chapter01:Asynchrony:Now&Later6. Chapter02:Callbacks7. Chapter03:Promises8. Chapter04:Generators9. Chapter05:ProgramPerformance10. Chapter06:Benchmarking&Tuning11. AppendixA:`asynquence`Library12. AppendixB:AdvancedAsyncPatterns13. AppendixC:Acknowledgments

TableofContents

Page 3: You Don't Know JS: Async & Performance

Purchasedigital/printcopyfromO'Reilly

TableofContents

Foreword(byJakeArchibald)PrefaceChapter1:Asynchrony:Now&LaterChapter2:CallbacksChapter3:PromisesChapter4:GeneratorsChapter5:ProgramPerformanceChapter6:Benchmarking&TuningAppendixA:Library:asynquenceAppendixB:AdvancedAsyncPatternsAppendixC:ThankYou's!

YouDon'tKnowJS:Async&Performance

Page 4: You Don't Know JS: Async & Performance

I'msureyounoticed,but"JS"inthebookseriestitleisnotanabbreviationforwordsusedtocurseaboutJavaScript,thoughcursingatthelanguage'squirksissomethingwecanprobablyallidentifywith!

Fromtheearliestdaysoftheweb,JavaScripthasbeenafoundationaltechnologythatdrivesinteractiveexperiencearoundthecontentweconsume.Whileflickeringmousetrailsandannoyingpop-uppromptsmaybewhereJavaScriptstarted,nearly2decadeslater,thetechnologyandcapabilityofJavaScripthasgrownmanyordersofmagnitude,andfewdoubtitsimportanceattheheartoftheworld'smostwidelyavailablesoftwareplatform:theweb.

Butasalanguage,ithasperpetuallybeenatargetforagreatdealofcriticism,owingpartlytoitsheritagebutevenmoretoitsdesignphilosophy.Eventhenameevokes,asBrendanEichonceputit,"dumbkidbrother"statusnexttoitsmorematureolderbrother"Java".Butthenameismerelyanaccidentofpoliticsandmarketing.Thetwolanguagesarevastlydifferentinmanyimportantways."JavaScript"isasrelatedto"Java"as"Carnival"isto"Car".

BecauseJavaScriptborrowsconceptsandsyntaxidiomsfromseverallanguages,includingproudC-styleproceduralrootsaswellassubtle,lessobviousScheme/Lisp-stylefunctionalroots,itisexceedinglyapproachabletoabroadaudienceofdevelopers,eventhosewithjustlittletonoprogrammingexperience.The"HelloWorld"ofJavaScriptissosimplethatthelanguageisinvitingandeasytogetcomfortablewithinearlyexposure.

WhileJavaScriptisperhapsoneoftheeasiestlanguagestogetupandrunningwith,itseccentricitiesmakesolidmasteryofthelanguageavastlylesscommonoccurrencethaninmanyotherlanguages.Whereittakesaprettyin-depthknowledgeofalanguagelikeCorC++towriteafull-scaleprogram,full-scaleproductionJavaScriptcan,andoftendoes,barelyscratchthesurfaceofwhatthelanguagecando.

Sophisticatedconceptswhicharedeeplyrootedintothelanguagetendinsteadtosurfacethemselvesinseeminglysimplisticways,suchaspassingaroundfunctionsascallbacks,whichencouragestheJavaScriptdevelopertojustusethelanguageas-isandnotworrytoomuchaboutwhat'sgoingonunderthehood.

Itissimultaneouslyasimple,easy-to-uselanguagethathasbroadappeal,andacomplexandnuancedcollectionoflanguagemechanicswhichwithoutcarefulstudywilleludetrueunderstandingevenforthemostseasonedofJavaScriptdevelopers.

ThereinliestheparadoxofJavaScript,theAchilles'Heelofthelanguage,thechallengewearepresentlyaddressing.BecauseJavaScriptcanbeusedwithoutunderstanding,theunderstandingofthelanguageisoftenneverattained.

IfateverypointthatyouencounterasurpriseorfrustrationinJavaScript,yourresponseistoaddittotheblacklist,assomeareaccustomedtodoing,yousoonwillberelegatedtoahollowshelloftherichnessofJavaScript.

Whilethissubsethasbeenfamouslydubbed"TheGoodParts",Iwouldimploreyou,dearreader,toinsteadconsideritthe"TheEasyParts","TheSafeParts",oreven"TheIncompleteParts".

ThisYouDon'tKnowJavaScriptbookseriesoffersacontrarychallenge:learnanddeeplyunderstandallofJavaScript,evenandespecially"TheToughParts".

Here,weaddressheadonthetendencyofJSdeveloperstolearn"justenough"togetby,withouteverforcingthemselvestolearnexactlyhowandwhythelanguagebehavesthewayitdoes.Furthermore,weeschewthecommonadvicetoretreatwhentheroadgetsrough.

Iamnotcontent,norshouldyoube,atstoppingoncesomethingjustworks,andnotreallyknowingwhy.Igentlychallenge

YouDon'tKnowJS

Preface

Mission

Page 5: You Don't Know JS: Async & Performance

youtojourneydownthatbumpy"roadlesstraveled"andembraceallthatJavaScriptisandcando.Withthatknowledge,notechnique,noframework,nopopularbuzzwordacronymoftheweek,willbebeyondyourunderstanding.

Thesebookseachtakeonspecificcorepartsofthelanguagewhicharemostcommonlymisunderstoodorunder-understood,anddiveverydeepandexhaustivelyintothem.Youshouldcomeawayfromreadingwithafirmconfidenceinyourunderstanding,notjustofthetheoretical,butthepractical"whatyouneedtoknow"bits.

TheJavaScriptyouknowrightnowisprobablypartshandeddowntoyoubyotherswho'vebeenburnedbyincompleteunderstanding.ThatJavaScriptisbutashadowofthetruelanguage.Youdon'treallyknowJavaScript,yet,butifyoudigintothisseries,youwill.Readon,myfriends.JavaScriptawaitsyou.

JavaScriptisawesome.It'seasytolearnpartially,andmuchhardertolearncompletely(orevensufficiently).Whendevelopersencounterconfusion,theyusuallyblamethelanguageinsteadoftheirlackofunderstanding.Thesebooksaimtofixthat,inspiringastrongappreciationforthelanguageyoucannow,andshould,deeplyknow.

Note:Manyoftheexamplesinthisbookassumemodern(andfuture-reaching)JavaScriptengineenvironments,suchasES6.Somecodemaynotworkasdescribedifruninolder(pre-ES6)engines.

Summary

Page 6: You Don't Know JS: Async & Performance

Overtheyears,myemployerhastrustedmeenoughtoconductinterviews.Ifwe'relookingforsomeonewithskillsinJavaScript,myfirstlineofquestioning…actuallythat'snottrue,Ifirstcheckifthecandidateneedsthebathroomand/oradrink,becausecomfortisimportant,butonceI'mpastthebitaboutthecandidate'sfluidin/out-take,IsetaboutdeterminingifthecandidateknowsJavaScript,orjustjQuery.

Notthatthere'sanythingwrongwithjQuery.ItletsyoudoalotwithoutreallyknowingJavaScript,andthat'safeaturenotabug.ButifthejobcallsforadvancedskillsinJavaScriptperformanceandmaintainability,youneedsomeonewhoknowshowlibrariessuchasjQueryareputtogether.YouneedtobeabletoharnessthecoreofJavaScriptthesamewaytheydo.

IfIwanttogetapictureofsomeone'scoreJavaScriptskill,I'mmostinterestedinwhattheymakeofclosures(you'vereadthatbookofthisseriesalready,right?)andhowtogetthemostoutofasynchronicity,whichbringsustothisbook.

Forstarters,you'llbetakenthroughcallbacks,thebreadandbutterofasynchronousprogramming.Ofcourse,breadandbutterdoesnotmakeforaparticularlysatisfyingmeal,butthenextcourseisfulloftastytastypromises!

Ifyoudon'tknowpromises,nowisthetimetolearn.PromisesarenowtheofficialwaytoprovideasyncreturnvaluesinbothJavaScriptandtheDOM.AllfutureasyncDOMAPIswillusethem,manyalreadydo,sobeprepared!Atthetimeofwriting,Promiseshaveshippedinmostmajorbrowsers,withIEshippingsoon.Onceyou'vefinishedthat,Ihopeyouleftroomforthenextcourse,Generators.

GeneratorssnucktheirwayintostableversionsofChromeandFirefoxwithouttoomuchpompandceremony,because,frankly,they'remorecomplicatedthantheyareinteresting.Or,that'swhatIthoughtuntilIsawthemcombinedwithpromises.There,theybecomeanimportanttoolinreadabilityandmaintenance.

Fordessert,well,Iwon'tspoilthesurprise,butpreparetogazeintothefutureofJavaScript!Featuresthatgiveyoumoreandmorecontroloverconcurrencyandasynchronicity.

Well,Iwon'tblockyourenjoymentofthebookanylonger,onwiththeshow!Ifyou'vealreadyreadpartofthebookbeforereadingthisForeword,giveyourself10asynchronouspoints!Youdeservethem!

JakeArchibaldjakearchibald.com,@jaffathecakeDeveloperAdvocateatGoogleChrome

Page 7: You Don't Know JS: Async & Performance

ForewordPrefaceChapter1:Asynchrony:Now&Later

AProgramInChunksEventLoopParallelThreadingConcurrencyJobsStatementOrdering

Chapter2:CallbacksContinuationsSequentialBrainTrustIssuesTryingToSaveCallbacks

Chapter3:PromisesWhatisaPromise?ThenableDuck-TypingPromiseTrustChainFlowErrorHandlingPromisePatternsPromiseAPIRecapPromiseLimitations

Chapter4:GeneratorsBreakingRun-to-completionGenerator'ingValuesIteratingGeneratorsAsynchronouslyGenerators+PromisesGeneratorDelegationGeneratorConcurrencyThunksPre-ES6Generators

Chapter5:ProgramPerformanceWebWorkersParallelJSSIMDasm.js

Chapter6:Benchmarking&TuningBenchmarkingContextIsKingjsPerf.comWritingGoodTestsMicroperformanceTailCallOptimization(TCO)

AppendixA:asynquenceLibraryAppendixB:AdvancedAsyncPatternsAppendixC:Acknowledgments

YouDon'tKnowJS:Async&Performance

TableofContents

Page 8: You Don't Know JS: Async & Performance

OneofthemostimportantandyetoftenmisunderstoodpartsofprogramminginalanguagelikeJavaScriptishowtoexpressandmanipulateprogrambehaviorspreadoutoveraperiodoftime.

Thisisnotjustaboutwhathappensfromthebeginningofaforlooptotheendofaforloop,whichofcoursetakessometime(microsecondstomilliseconds)tocomplete.It'saboutwhathappenswhenpartofyourprogramrunsnow,andanotherpartofyourprogramrunslater--there'sagapbetweennowandlaterwhereyourprogramisn'tactivelyexecuting.

Practicallyallnontrivialprogramseverwritten(especiallyinJS)haveinsomewayoranotherhadtomanagethisgap,whetherthatbeinwaitingforuserinput,requestingdatafromadatabaseorfilesystem,sendingdataacrossthenetworkandwaitingforaresponse,orperformingarepeatedtaskatafixedintervaloftime(likeanimation).Inallthesevariousways,yourprogramhastomanagestateacrossthegapintime.AstheyfamouslysayinLondon(ofthechasmbetweenthesubwaydoorandtheplatform):"mindthegap."

Infact,therelationshipbetweenthenowandlaterpartsofyourprogramisattheheartofasynchronousprogramming.

AsynchronousprogramminghasbeenaroundsincethebeginningofJS,forsure.ButmostJSdevelopershaveneverreallycarefullyconsideredexactlyhowandwhyitcropsupintheirprograms,orexploredvariousotherwaystohandleit.Thegoodenoughapproachhasalwaysbeenthehumblecallbackfunction.Manytothisdaywillinsistthatcallbacksaremorethansufficient.

ButasJScontinuestogrowinbothscopeandcomplexity,tomeettheever-wideningdemandsofafirst-classprogramminglanguagethatrunsinbrowsersandserversandeveryconceivabledeviceinbetween,thepainsbywhichwemanageasynchronyarebecomingincreasinglycrippling,andtheycryoutforapproachesthatarebothmorecapableandmorereason-able.

Whilethisallmayseemratherabstractrightnow,Iassureyouwe'lltackleitmorecompletelyandconcretelyaswegoonthroughthisbook.We'llexploreavarietyofemergingtechniquesforasyncJavaScriptprogrammingoverthenextseveralchapters.

Butbeforewecangetthere,we'regoingtohavetounderstandmuchmoredeeplywhatasynchronyisandhowitoperatesinJS.

YoumaywriteyourJSprograminone.jsfile,butyourprogramisalmostcertainlycomprisedofseveralchunks,onlyoneofwhichisgoingtoexecutenow,andtherestofwhichwillexecutelater.Themostcommonunitofchunkisthefunction.

TheproblemmostdevelopersnewtoJSseemtohaveisthatlaterdoesn'thappenstrictlyandimmediatelyafternow.Inotherwords,tasksthatcannotcompletenoware,bydefinition,goingtocompleteasynchronously,andthuswewillnothaveblockingbehaviorasyoumightintuitivelyexpectorwant.

Consider:

//ajax(..)issomearbitraryAjaxfunctiongivenbyalibrary

vardata=ajax("http://some.url.1");

console.log(data);

//Oops!`data`generallywon'thavetheAjaxresults

You'reprobablyawarethatstandardAjaxrequestsdon'tcompletesynchronously,whichmeanstheajax(..)functiondoes

YouDon'tKnowJS:Async&Performance

Chapter1:Asynchrony:Now&Later

APrograminChunks

Page 9: You Don't Know JS: Async & Performance

notyethaveanyvaluetoreturnbacktobeassignedtodatavariable.Ifajax(..)couldblockuntiltheresponsecameback,thenthedata=..assignmentwouldworkfine.

Butthat'snothowwedoAjax.WemakeanasynchronousAjaxrequestnow,andwewon'tgettheresultsbackuntillater.

Thesimplest(butdefinitelynotonly,ornecessarilyevenbest!)wayof"waiting"fromnowuntillateristouseafunction,commonlycalledacallbackfunction:

//ajax(..)issomearbitraryAjaxfunctiongivenbyalibrary

ajax("http://some.url.1",functionmyCallbackFunction(data){

console.log(data);//Yay,Igotsmesome`data`!

});

Warning:Youmayhaveheardthatit'spossibletomakesynchronousAjaxrequests.Whilethat'stechnicallytrue,youshouldnever,everdoit,underanycircumstances,becauseitlocksthebrowserUI(buttons,menus,scrolling,etc.)andpreventsanyuserinteractionwhatsoever.Thisisaterribleidea,andshouldalwaysbeavoided.

Beforeyouprotestindisagreement,no,yourdesiretoavoidthemessofcallbacksisnotjustificationforblocking,synchronousAjax.

Forexample,considerthiscode:

functionnow(){

return21;

}

functionlater(){

answer=answer*2;

console.log("Meaningoflife:",answer);

}

varanswer=now();

setTimeout(later,1000);//Meaningoflife:42

Therearetwochunkstothisprogram:thestuffthatwillrunnow,andthestuffthatwillrunlater.Itshouldbefairlyobviouswhatthosetwochunksare,butlet'sbesuperexplicit:

Now:

functionnow(){

return21;

}

functionlater(){..}

varanswer=now();

setTimeout(later,1000);

Later:

answer=answer*2;

console.log("Meaningoflife:",answer);

Thenowchunkrunsrightaway,assoonasyouexecuteyourprogram.ButsetTimeout(..)alsosetsupanevent(atimeout)tohappenlater,sothecontentsofthelater()functionwillbeexecutedatalatertime(1,000millisecondsfromnow).

Page 10: You Don't Know JS: Async & Performance

Anytimeyouwrapaportionofcodeintoafunctionandspecifythatitshouldbeexecutedinresponsetosomeevent(timer,mouseclick,Ajaxresponse,etc.),youarecreatingalaterchunkofyourcode,andthusintroducingasynchronytoyourprogram.

Thereisnospecificationorsetofrequirementsaroundhowtheconsole.*methodswork--theyarenotofficiallypartofJavaScript,butareinsteadaddedtoJSbythehostingenvironment(seetheTypes&Grammartitleofthisbookseries).

So,differentbrowsersandJSenvironmentsdoastheyplease,whichcansometimesleadtoconfusingbehavior.

Inparticular,therearesomebrowsersandsomeconditionsthatconsole.log(..)doesnotactuallyimmediatelyoutputwhatit'sgiven.ThemainreasonthismayhappenisbecauseI/Oisaveryslowandblockingpartofmanyprograms(notjustJS).So,itmayperformbetter(fromthepage/UIperspective)forabrowsertohandleconsoleI/Oasynchronouslyinthebackground,withoutyouperhapsevenknowingthatoccurred.

Anotterriblycommon,butpossible,scenariowherethiscouldbeobservable(notfromcodeitselfbutfromtheoutside):

vara={

index:1

};

//later

console.log(a);//??

//evenlater

a.index++;

We'dnormallyexpecttoseetheaobjectbesnapshottedattheexactmomentoftheconsole.log(..)statement,printingsomethinglike{index:1},suchthatinthenextstatmentwhena.index++happens,it'smodifyingsomethingdifferentthan,orjuststrictlyafter,theoutputofa.

Mostofthetime,theprecedingcodewillprobablyproduceanobjectrepresentationinyourdevelopertools'consolethat'swhatyou'dexpect.Butit'spossiblethissamecodecouldruninasituationwherethebrowserfeltitneededtodefertheconsoleI/Otothebackground,inwhichcaseit'spossiblethatbythetimetheobjectisrepresentedinthebrowserconsole,thea.index++hasalreadyhappened,anditshows{index:2}.

It'samovingtargetunderwhatconditionsexactlyconsoleI/Owillbedeferred,orevenwhetheritwillbeobservable.JustbeawareofthispossibleasynchronicityinI/Oincaseyoueverrunintoissuesindebuggingwhereobjectshavebeenmodifiedafteraconsole.log(..)statementandyetyouseetheunexpectedmodificationsshowup.

Note:Ifyourunintothisrarescenario,thebestoptionistousebreakpointsinyourJSdebuggerinsteadofrelyingonconsoleoutput.Thenextbestoptionwouldbetoforcea"snapshot"oftheobjectinquestionbyserializingittoastring,likewithJSON.stringify(..).

Let'smakea(perhapsshocking)claim:despiteyourclearlybeingabletowriteasynchronousJScode(likethetimeoutwejustlookedat),upuntilrecently(ES6),JavaScriptitselfhasactuallyneverhadanydirectnotionofasynchronybuiltintoit.

What!?Thatseemslikeacrazyclaim,right?Infact,it'squitetrue.TheJSengineitselfhasneverdoneanythingmorethanexecuteasinglechunkofyourprogramatanygivenmoment,whenaskedto.

"Askedto."Bywhom?That'stheimportantpart!

TheJSenginedoesn'truninisolation.Itrunsinsideahostingenvironment,whichisformostdevelopersthetypicalwebbrowser.Overthelastseveralyears(butbynomeansexlusively),JShasexpandedbeyondthebrowserintootherenvironments,suchasservers,viathingslikeNode.js.Infact,JavaScriptgetsembeddedintoallkindsofdevicesthese

AsyncConsole

EventLoop

Page 11: You Don't Know JS: Async & Performance

days,fromrobotstolightbulbs.

Buttheonecommon"thread"(that'sanot-so-subtleasynchronousjoke,forwhatit'sworth)ofalltheseenvironmentsisthattheyhaveamechanisminthemthathandlesexecutingmultiplechunksofyourprogramovertime,ateachmomentinvokingtheJSengine,calledthe"eventloop."

Inotherwords,theJSenginehashadnoinnatesenseoftime,buthasinsteadbeenanon-demandexecutionenvironmentforanyarbitrarysnippetofJS.It'sthesurroundingenvironmentthathasalwaysscheduled"events"(JScodeexecutions).

So,forexample,whenyourJSprogrammakesanAjaxrequesttofetchsomedatafromaserver,yousetupthe"response"codeinafunction(commonlycalleda"callback"),andtheJSenginetellsthehostingenvironment,"Hey,I'mgoingtosuspendexecutionfornow,butwheneveryoufinishwiththatnetworkrequest,andyouhavesomedata,pleasecallthisfunctionback."

Thebrowseristhensetuptolistenfortheresponsefromthenetwork,andwhenithassomethingtogiveyou,itschedulesthecallbackfunctiontobeexecutedbyinsertingitintotheeventloop.

Sowhatistheeventloop?

Let'sconceptualizeitfirstthroughsomefake-ishcode:

//`eventLoop`isanarraythatactsasaqueue(first-in,first-out)

vareventLoop=[];

varevent;

//keepgoing"forever"

while(true){

//performa"tick"

if(eventLoop.length>0){

//getthenexteventinthequeue

event=eventLoop.shift();

//now,executethenextevent

try{

event();

}

catch(err){

reportError(err);

}

}

}

Thisis,ofcourse,vastlysimplifiedpseudocodetoillustratetheconcepts.Butitshouldbeenoughtohelpgetabetterunderstanding.

Asyoucansee,there'sacontinuouslyrunninglooprepresentedbythewhileloop,andeachiterationofthisloopiscalleda"tick."Foreachtick,ifaneventiswaitingonthequeue,it'stakenoffandexecuted.Theseeventsareyourfunctioncallbacks.

It'simportanttonotethatsetTimeout(..)doesn'tputyourcallbackontheeventloopqueue.Whatitdoesissetupatimer;whenthetimerexpires,theenvironmentplacesyourcallbackintotheeventloop,suchthatsomefuturetickwillpickitupandexecuteit.

Whatiftherearealready20itemsintheeventloopatthatmoment?Yourcallbackwaits.Itgetsinlinebehindtheothers--there'snotnormallyapathforpreemptingthequeueandskippingaheadinline.ThisexplainswhysetTimeout(..)timersmaynotfirewithperfecttemporalaccuracy.You'reguaranteed(roughlyspeaking)thatyourcallbackwon'tfirebeforethetimeintervalyouspecify,butitcanhappenatorafterthattime,dependingonthestateoftheeventqueue.

So,inotherwords,yourprogramisgenerallybrokenupintolotsofsmallchunks,whichhappenoneaftertheotherintheeventloopqueue.Andtechnically,othereventsnotrelateddirectlytoyourprogramcanbeinterleavedwithinthequeueaswell.

Note:Wementioned"upuntilrecently"inrelationtoES6changingthenatureofwheretheeventloopqueueismanaged.

Page 12: You Don't Know JS: Async & Performance

It'smostlyaformaltechnicality,butES6nowspecifieshowtheeventloopworks,whichmeanstechnicallyit'swithinthepurviewoftheJSengine,ratherthanjustthehostingenvironment.OnemainreasonforthischangeistheintroductionofES6Promises,whichwe'lldiscussinChapter3,becausetheyrequiretheabilitytohavedirect,fine-grainedcontroloverschedulingoperationsontheeventloopqueue(seethediscussionofsetTimeout(..0)inthe"Cooperation"section).

It'sverycommontoconflatetheterms"async"and"parallel,"buttheyareactuallyquitedifferent.Remember,asyncisaboutthegapbetweennowandlater.Butparallelisaboutthingsbeingabletooccursimultaneously.

Themostcommontoolsforparallelcomputingareprocessesandthreads.Processesandthreadsexecuteindependentlyandmayexecutesimultaneously:onseparateprocessors,orevenseparatecomputers,butmultiplethreadscansharethememoryofasingleprocess.

Aneventloop,bycontrast,breaksitsworkintotasksandexecutestheminserial,disallowingparallelaccessandchangestosharedmemory.Parallelismand"serialism"cancoexistintheformofcooperatingeventloopsinseparatethreads.

Theinterleavingofparallelthreadsofexecutionandtheinterleavingofasynchronouseventsoccuratverydifferentlevelsofgranularity.

Forexample:

functionlater(){

answer=answer*2;

console.log("Meaningoflife:",answer);

}

Whiletheentirecontentsoflater()wouldberegardedasasingleeventloopqueueentry,whenthinkingaboutathreadthiscodewouldrunon,there'sactuallyperhapsadozendifferentlow-leveloperations.Forexample,answer=answer*2requiresfirstloadingthecurrentvalueofanswer,thenputting2somewhere,thenperformingthemultiplication,thentakingtheresultandstoringitbackintoanswer.

Inasingle-threadedenvironment,itreallydoesn'tmatterthattheitemsinthethreadqueuearelow-leveloperations,becausenothingcaninterruptthethread.Butifyouhaveaparallelsystem,wheretwodifferentthreadsareoperatinginthesameprogram,youcouldverylikelyhaveunpredictablebehavior.

Consider:

vara=20;

functionfoo(){

a=a+1;

}

functionbar(){

a=a*2;

}

//ajax(..)issomearbitraryAjaxfunctiongivenbyalibrary

ajax("http://some.url.1",foo);

ajax("http://some.url.2",bar);

InJavaScript'ssingle-threadedbehavior,iffoo()runsbeforebar(),theresultisthatahas42,butifbar()runsbeforefoo()theresultinawillbe41.

IfJSeventssharingthesamedataexecutedinparallel,though,theproblemswouldbemuchmoresubtle.Considerthesetwolistsofpseudocodetasksasthethreadsthatcouldrespectivelyrunthecodeinfoo()andbar(),andconsiderwhathappensiftheyarerunningatexactlythesametime:

ParallelThreading

Page 13: You Don't Know JS: Async & Performance

Thread1(XandYaretemporarymemorylocations):

foo():

a.loadvalueof`a`in`X`

b.store`1`in`Y`

c.add`X`and`Y`,storeresultin`X`

d.storevalueof`X`in`a`

Thread2(XandYaretemporarymemorylocations):

bar():

a.loadvalueof`a`in`X`

b.store`2`in`Y`

c.multiply`X`and`Y`,storeresultin`X`

d.storevalueof`X`in`a`

Now,let'ssaythatthetwothreadsarerunningtrulyinparallel.Youcanprobablyspottheproblem,right?TheyusesharedmemorylocationsXandYfortheirtemporarysteps.

What'stheendresultinaifthestepshappenlikethis?

1a(loadvalueof`a`in`X`==>`20`)

2a(loadvalueof`a`in`X`==>`20`)

1b(store`1`in`Y`==>`1`)

2b(store`2`in`Y`==>`2`)

1c(add`X`and`Y`,storeresultin`X`==>`22`)

1d(storevalueof`X`in`a`==>`22`)

2c(multiply`X`and`Y`,storeresultin`X`==>`44`)

2d(storevalueof`X`in`a`==>`44`)

Theresultinawillbe44.Butwhataboutthisordering?

1a(loadvalueof`a`in`X`==>`20`)

2a(loadvalueof`a`in`X`==>`20`)

2b(store`2`in`Y`==>`2`)

1b(store`1`in`Y`==>`1`)

2c(multiply`X`and`Y`,storeresultin`X`==>`20`)

1c(add`X`and`Y`,storeresultin`X`==>`21`)

1d(storevalueof`X`in`a`==>`21`)

2d(storevalueof`X`in`a`==>`21`)

Theresultinawillbe21.

So,threadedprogrammingisverytricky,becauseifyoudon'ttakespecialstepstopreventthiskindofinterruption/interleavingfromhappening,youcangetverysurprising,nondeterministicbehaviorthatfrequentlyleadstoheadaches.

JavaScriptneversharesdataaccrossthreads,whichmeansthatlevelofnondeterminismisn'taconcern.Butthatdoesn'tmeanJSisalwaysdeterministic.Rememberearlier,wheretherelativeorderingoffoo()andbar()producestwodifferentresults(41or42)?

Note:Itmaynotbeobviousyet,butnotallnondeterminismisbad.Sometimesit'sirrelevant,andsometimesit'sintentional.We'llseemoreexamplesofthatthroughoutthisandthenextfewchapters.

BecauseofJavaScript'ssingle-threading,thecodeinsideoffoo()(andbar())isatomic,whichmeansthatoncefoo()startsrunning,theentiretyofitscodewillfinishbeforeanyofthecodeinbar()canrun,orviceversa.Thisiscalled"run-to-completion"behavior.

Run-to-Completion

Page 14: You Don't Know JS: Async & Performance

Infact,therun-to-completionsemanticsaremoreobviouswhenfoo()andbar()havemorecodeinthem,suchas:

vara=1;

varb=2;

functionfoo(){

a++;

b=b*a;

a=b+3;

}

functionbar(){

b--;

a=8+b;

b=a*2;

}

//ajax(..)issomearbitraryAjaxfunctiongivenbyalibrary

ajax("http://some.url.1",foo);

ajax("http://some.url.2",bar);

Becausefoo()can'tbeinterruptedbybar(),andbar()can'tbeinterruptedbyfoo(),thisprogramonlyhastwopossibleoutcomesdependingonwhichstartsrunningfirst--ifthreadingwerepresent,andtheindividualstatementsinfoo()andbar()couldbeinterleaved,thenumberofpossibleoutcomeswouldbegreatlyincreased!

Chunk1issynchronous(happensnow),butchunks2and3areasynchronous(happenlater),whichmeanstheirexecutionwillbeseparatedbyagapoftime.

Chunk1:

vara=1;

varb=2;

Chunk2(foo()):

a++;

b=b*a;

a=b+3;

Chunk3(bar()):

b--;

a=8+b;

b=a*2;

Chunks2and3mayhappenineither-firstorder,sotherearetwopossibleoutcomesforthisprogram,asillustratedhere:

Outcome1:

vara=1;

varb=2;

//foo()

a++;

b=b*a;

a=b+3;

//bar()

b--;

a=8+b;

b=a*2;

a;//11

Page 15: You Don't Know JS: Async & Performance

b;//22

Outcome2:

vara=1;

varb=2;

//bar()

b--;

a=8+b;

b=a*2;

//foo()

a++;

b=b*a;

a=b+3;

a;//183

b;//180

Twooutcomesfromthesamecodemeanswestillhavenondeterminism!Butit'satthefunction(event)orderinglevel,ratherthanatthestatementorderinglevel(or,infact,theexpressionoperationorderinglevel)asitiswiththreads.Inotherwords,it'smoredeterministicthanthreadswouldhavebeen.

AsappliedtoJavaScript'sbehavior,thisfunction-orderingnondeterminismisthecommonterm"racecondition,"asfoo()andbar()areracingagainsteachothertoseewhichrunsfirst.Specifically,it'sa"racecondition"becauseyoucannotpredictreliablyhowaandbwillturnout.

Note:IftherewasafunctioninJSthatsomehowdidnothaverun-to-completionbehavior,wecouldhavemanymorepossibleoutcomes,right?ItturnsoutES6introducesjustsuchathing(seeChapter4"Generators"),butdon'tworryrightnow,we'llcomebacktothat!

Let'simagineasitethatdisplaysalistofstatusupdates(likeasocialnetworknewsfeed)thatprogressivelyloadsastheuserscrollsdownthelist.Tomakesuchafeatureworkcorrectly,(atleast)twoseparate"processes"willneedtobeexecutingsimultaneously(i.e.,duringthesamewindowoftime,butnotnecessarilyatthesameinstant).

Note:We'reusing"process"inquotesherebecausetheyaren'ttrueoperatingsystem–levelprocessesinthecomputersciencesense.They'revirtualprocesses,ortasks,thatrepresentalogicallyconnected,sequentialseriesofoperations.We'llsimplyprefer"process"over"task"becauseterminology-wise,itwillmatchthedefinitionsoftheconceptswe'reexploring.

Thefirst"process"willrespondtoonscrollevents(makingAjaxrequestsfornewcontent)astheyfirewhentheuserhasscrolledthepagefurtherdown.Thesecond"process"willreceiveAjaxresponsesback(torendercontentontothepage).

Obviously,ifauserscrollsfastenough,youmayseetwoormoreonscrolleventsfiredduringthetimeittakestogetthefirstresponsebackandprocess,andthusyou'regoingtohaveonscrolleventsandAjaxresponseeventsfiringrapidly,interleavedwitheachother.

Concurrencyiswhentwoormore"processes"areexecutingsimultaneouslyoverthesameperiod,regardlessofwhethertheirindividualconstituentoperationshappeninparallel(atthesameinstantonseparateprocessorsorcores)ornot.Youcanthinkofconcurrencythenas"process"-level(ortask-level)parallelism,asopposedtooperation-levelparallelism(separate-processorthreads).

Note:Concurrencyalsointroducesanoptionalnotionofthese"processes"interactingwitheachother.We'llcomebacktothatlater.

Foragivenwindowoftime(afewsecondsworthofauserscrolling),let'svisualizeeachindependent"process"asaseries

Concurrency

Page 16: You Don't Know JS: Async & Performance

ofevents/operations:

"Process"1(onscrollevents):

onscroll,request1

onscroll,request2

onscroll,request3

onscroll,request4

onscroll,request5

onscroll,request6

onscroll,request7

"Process"2(Ajaxresponseevents):

response1

response2

response3

response4

response5

response6

response7

It'squitepossiblethatanonscrolleventandanAjaxresponseeventcouldbereadytobeprocessedatexactlythesamemoment.Forexamplelet'svisualizetheseeventsinatimeline:

onscroll,request1

onscroll,request2response1

onscroll,request3response2

response3

onscroll,request4

onscroll,request5

onscroll,request6response4

onscroll,request7

response6

response5

response7

But,goingbacktoournotionoftheeventloopfromearilerinthechapter,JSisonlygoingtobeabletohandleoneeventatatime,soeitheronscroll,request2isgoingtohappenfirstorresponse1isgoingtohappenfirst,buttheycannothappenatliterallythesamemoment.Justlikekidsataschoolcafeteria,nomatterwhatcrowdtheyformoutsidethedoors,they'llhavetomergeintoasinglelinetogettheirlunch!

Let'svisualizetheinterleavingofalltheseeventsontotheeventloopqueue.

EventLoopQueue:

onscroll,request1<---Process1starts

onscroll,request2

response1<---Process2starts

onscroll,request3

response2

response3

onscroll,request4

onscroll,request5

onscroll,request6

response4

onscroll,request7<---Process1finishes

response6

response5

response7<---Process2finishes

"Process1"and"Process2"runconcurrently(task-levelparallel),buttheirindividualeventsrunsequentiallyontheeventloopqueue.

Page 17: You Don't Know JS: Async & Performance

Bytheway,noticehowresponse6andresponse5camebackoutofexpectedorder?

Thesingle-threadedeventloopisoneexpressionofconcurrency(therearecertainlyothers,whichwe'llcomebacktolater).

Astwoormore"processes"areinterleavingtheirsteps/eventsconcurrentlywithinthesameprogram,theydon'tnecessarilyneedtointeractwitheachotherifthetasksareunrelated.Iftheydon'tinteract,nondeterminismisperfectlyacceptable.

Forexample:

varres={};

functionfoo(results){

res.foo=results;

}

functionbar(results){

res.bar=results;

}

//ajax(..)issomearbitraryAjaxfunctiongivenbyalibrary

ajax("http://some.url.1",foo);

ajax("http://some.url.2",bar);

foo()andbar()aretwoconcurrent"processes,"andit'snondeterminatewhichordertheywillbefiredin.Butwe'veconstructedtheprogramsoitdoesn'tmatterwhatordertheyfirein,becausetheyactindependentlyandassuchdon'tneedtointeract.

Thisisnota"racecondition"bug,asthecodewillalwaysworkcorrectly,regardlessoftheordering.

Morecommonly,concurrent"processes"willbynecessityinteract,indirectlythroughscopeand/ortheDOM.Whensuchinteractionwilloccur,youneedtocoordinatetheseinteractionstoprevent"raceconditions,"asdescribedearlier.

Here'sasimpleexampleoftwoconcurrent"processes"thatinteractbecauseofimpliedordering,whichisonlysometimesbroken:

varres=[];

functionresponse(data){

res.push(data);

}

//ajax(..)issomearbitraryAjaxfunctiongivenbyalibrary

ajax("http://some.url.1",response);

ajax("http://some.url.2",response);

Theconcurrent"processes"arethetworesponse()callsthatwillbemadetohandletheAjaxresponses.Theycanhappenineither-firstorder.

Let'sassumetheexpectedbehavioristhatres[0]hastheresultsofthe"http://some.url.1"call,andres[1]hastheresultsofthe"http://some.url.2"call.Sometimesthatwillbethecase,butsometimesthey'llbeflipped,dependingonwhichcallfinishesfirst.There'saprettygoodlikelihoodthatthisnondeterminismisa"racecondition"bug.

Note:Beextremelywaryofassumptionsyoumighttendtomakeinthesesituations.Forexample,it'snotuncommonforadevelopertoobservethat"http://some.url.2"is"always"muchslowertorespondthan"http://some.url.1",perhapsbyvirtueofwhattasksthey'redoing(e.g.,oneperformingadatabasetaskandtheotherjustfetchingastaticfile),sothe

Noninteracting

Interaction

Page 18: You Don't Know JS: Async & Performance

observedorderingseemstoalwaysbeasexpected.Evenifbothrequestsgotothesameserver,anditintentionallyrespondsinacertainorder,there'snorealguaranteeofwhatordertheresponseswillarrivebackinthebrowser.

So,toaddresssucharacecondition,youcancoordinateorderinginteraction:

varres=[];

functionresponse(data){

if(data.url=="http://some.url.1"){

res[0]=data;

}

elseif(data.url=="http://some.url.2"){

res[1]=data;

}

}

//ajax(..)issomearbitraryAjaxfunctiongivenbyalibrary

ajax("http://some.url.1",response);

ajax("http://some.url.2",response);

RegardlessofwhichAjaxresponsecomesbackfirst,weinspectthedata.url(assumingoneisreturnedfromtheserver,ofcourse!)tofigureoutwhichpositiontheresponsedatashouldoccupyintheresarray.res[0]willalwaysholdthe"http://some.url.1"resultsandres[1]willalwaysholdthe"http://some.url.2"results.Throughsimplecoordination,weeliminatedthe"racecondition"nondeterminism.

ThesamereasoningfromthisscenariowouldapplyifmultipleconcurrentfunctioncallswereinteractingwitheachotherthroughthesharedDOM,likeoneupdatingthecontentsofa<div>andtheotherupdatingthestyleorattributesofthe<div>(e.g.,tomaketheDOMelementvisibleonceithascontent).Youprobablywouldn'twanttoshowtheDOMelementbeforeithadcontent,sothecoordinationmustensureproperorderinginteraction.

Someconcurrencyscenariosarealwaysbroken(notjustsometimes)withoutcoordinatedinteraction.Consider:

vara,b;

functionfoo(x){

a=x*2;

baz();

}

functionbar(y){

b=y*2;

baz();

}

functionbaz(){

console.log(a+b);

}

//ajax(..)issomearbitraryAjaxfunctiongivenbyalibrary

ajax("http://some.url.1",foo);

ajax("http://some.url.2",bar);

Inthisexample,whetherfoo()orbar()firesfirst,itwillalwayscausebaz()toruntooearly(eitheraorbwillstillbeundefined),butthesecondinvocationofbaz()willwork,asbothaandbwillbeavailable.

Therearedifferentwaystoaddresssuchacondition.Here'sonesimpleway:

vara,b;

functionfoo(x){

a=x*2;

if(a&&b){

baz();

}

}

functionbar(y){

Page 19: You Don't Know JS: Async & Performance

b=y*2;

if(a&&b){

baz();

}

}

functionbaz(){

console.log(a+b);

}

//ajax(..)issomearbitraryAjaxfunctiongivenbyalibrary

ajax("http://some.url.1",foo);

ajax("http://some.url.2",bar);

Theif(a&&b)conditionalaroundthebaz()callistraditionallycalleda"gate,"becausewe'renotsurewhatorderaandbwillarrive,butwewaitforbothofthemtogettherebeforeweproceedtoopenthegate(callbaz()).

Anotherconcurrencyinteractionconditionyoumayrunintoissometimescalleda"race,"butmorecorrectlycalleda"latch."It'scharacterizedby"onlythefirstonewins"behavior.Here,nondeterminismisacceptable,inthatyouareexplicitlysayingit'sOKforthe"race"tothefinishlinetohaveonlyonewinner.

Considerthisbrokencode:

vara;

functionfoo(x){

a=x*2;

baz();

}

functionbar(x){

a=x/2;

baz();

}

functionbaz(){

console.log(a);

}

//ajax(..)issomearbitraryAjaxfunctiongivenbyalibrary

ajax("http://some.url.1",foo);

ajax("http://some.url.2",bar);

Whicheverone(foo()orbar())fireslastwillnotonlyoverwritetheassignedavaluefromtheother,butitwillalsoduplicatethecalltobaz()(likelyundesired).

So,wecancoordinatetheinteractionwithasimplelatch,toletonlythefirstonethrough:

vara;

functionfoo(x){

if(!a){

a=x*2;

baz();

}

}

functionbar(x){

if(!a){

a=x/2;

baz();

}

}

functionbaz(){

console.log(a);

}

//ajax(..)issomearbitraryAjaxfunctiongivenbyalibrary

ajax("http://some.url.1",foo);

ajax("http://some.url.2",bar);

Page 20: You Don't Know JS: Async & Performance

Theif(!a)conditionalallowsonlythefirstoffoo()orbar()through,andthesecond(andindeedanysubsequent)callswouldjustbeignored.There'sjustnovirtueincominginsecondplace!

Note:Inallthesescenarios,we'vebeenusingglobalvariablesforsimplisticillustrationpurposes,butthere'snothingaboutourreasoningherethatrequiresit.Aslongasthefunctionsinquestioncanaccessthevariables(viascope),they'llworkasintended.Relyingonlexicallyscopedvariables(seetheScope&Closurestitleofthisbookseries),andinfactglobalvariablesasintheseexamples,isoneobviousdownsidetotheseformsofconcurrencycoordination.Aswegothroughthenextfewchapters,we'llseeotherwaysofcoordinationthataremuchcleanerinthatrespect.

Anotherexpressionofconcurrencycoordinationiscalled"cooperativeconcurrency."Here,thefocusisn'tsomuchoninteractingviavaluesharinginscopes(thoughthat'sobviouslystillallowed!).Thegoalistotakealong-running"process"andbreakitupintostepsorbatchessothatotherconcurrent"processes"haveachancetointerleavetheiroperationsintotheeventloopqueue.

Forexample,consideranAjaxresponsehandlerthatneedstorunthroughalonglistofresultstotransformthevalues.We'lluseArray#map(..)tokeepthecodeshorter:

varres=[];

//`response(..)`receivesarrayofresultsfromtheAjaxcall

functionresponse(data){

//addontoexisting`res`array

res=res.concat(

//makeanewtransformedarraywithall`data`valuesdoubled

data.map(function(val){

returnval*2;

})

);

}

//ajax(..)issomearbitraryAjaxfunctiongivenbyalibrary

ajax("http://some.url.1",response);

ajax("http://some.url.2",response);

If"http://some.url.1"getsitsresultsbackfirst,theentirelistwillbemappedintoresallatonce.Ifit'safewthousandorlessrecords,thisisnotgenerallyabigdeal.Butifit'ssay10millionrecords,thatcantakeawhiletorun(severalsecondsonapowerfullaptop,muchlongeronamobiledevice,etc.).

Whilesucha"process"isrunning,nothingelseinthepagecanhappen,includingnootherresponse(..)calls,noUIupdates,notevenusereventslikescrolling,typing,buttonclicking,andthelike.That'sprettypainful.

So,tomakeamorecooperativelyconcurrentsystem,onethat'sfriendlieranddoesn'thogtheeventloopqueue,youcanprocesstheseresultsinasynchronousbatches,aftereachone"yielding"backtotheeventlooptoletotherwaitingeventshappen.

Here'saverysimpleapproach:

varres=[];

//`response(..)`receivesarrayofresultsfromtheAjaxcall

functionresponse(data){

//let'sjustdo1000atatime

varchunk=data.splice(0,1000);

//addontoexisting`res`array

res=res.concat(

//makeanewtransformedarraywithall`chunk`valuesdoubled

chunk.map(function(val){

returnval*2;

})

);

Cooperation

Page 21: You Don't Know JS: Async & Performance

//anythinglefttoprocess?

if(data.length>0){

//asyncschedulenextbatch

setTimeout(function(){

response(data);

},0);

}

}

//ajax(..)issomearbitraryAjaxfunctiongivenbyalibrary

ajax("http://some.url.1",response);

ajax("http://some.url.2",response);

Weprocessthedatasetinmaximum-sizedchunksof1,000items.Bydoingso,weensureashort-running"process,"evenifthatmeansmanymoresubsequent"processes,"astheinterleavingontotheeventloopqueuewillgiveusamuchmoreresponsive(performant)site/app.

Ofcourse,we'renotinteraction-coordinatingtheorderingofanyofthese"processes,"sotheorderofresultsinreswon'tbepredictable.Iforderingwasrequired,you'dneedtouseinteractiontechniqueslikethosewediscussedearlier,oroneswewillcoverinlaterchaptersofthisbook.

WeusethesetTimeout(..0)(hack)forasyncscheduling,whichbasicallyjustmeans"stickthisfunctionattheendofthecurrenteventloopqueue."

Note:setTimeout(..0)isnottechnicallyinsertinganitemdirectlyontotheeventloopqueue.Thetimerwillinserttheeventatitsnextopportunity.Forexample,twosubsequentsetTimeout(..0)callswouldnotbestrictlyguaranteedtobeprocessedincallorder,soitispossibletoseevariousconditionsliketimerdriftwheretheorderingofsucheventsisn'tpredictable.InNode.js,asimilarapproachisprocess.nextTick(..).Despitehowconvenient(andusuallymoreperformant)itwouldbe,there'snotasingledirectway(atleastyet)acrossallenvironmentstoensureasynceventordering.Wecoverthistopicinmoredetailinthenextsection.

AsofES6,there'sanewconceptlayeredontopoftheeventloopqueue,calledthe"Jobqueue."Themostlikelyexposureyou'llhavetoitiswiththeasynchronousbehaviorofPromises(seeChapter3).

Unfortunately,atthemomentit'samechanismwithoutanexposedAPI,andthusdemonstratingitisabitmoreconvoluted.Sowe'regoingtohavetojustdescribeitconceptually,suchthatwhenwediscussasyncbehaviorwithPromisesinChapter3,you'llunderstandhowthoseactionsarebeingscheduledandprocessed.

So,thebestwaytothinkaboutthisthatI'vefoundisthatthe"Jobqueue"isaqueuehangingofftheendofeverytickintheeventloopqueue.Certainasync-impliedactionsthatmayoccurduringatickoftheeventloopwillnotcauseawholeneweventtobeaddedtotheeventloopqueue,butwillinsteadaddanitem(akaJob)totheendofthecurrenttick'sJobqueue.

It'skindalikesaying,"oh,here'sthisotherthingIneedtodolater,butmakesureithappensrightawaybeforeanythingelsecanhappen."

Or,touseametaphor:theeventloopqueueislikeanamusementparkride,whereonceyoufinishtheride,youhavetogotothebackofthelinetorideagain.ButtheJobqueueislikefinishingtheride,butthencuttinginlineandgettingrightbackon.

AJobcanalsocausemoreJobstobeaddedtotheendofthesamequeue.So,it'stheoreticallypossiblethataJob"loop"(aJobthatkeepsaddinganotherJob,etc.)couldspinindefinitely,thusstarvingtheprogramoftheabilitytomoveontothenexteventlooptick.Thiswouldconceptuallybealmostthesameasjustexpressingalong-runningorinfiniteloop(likewhile(true)..)inyourcode.

JobsarekindoflikethespiritofthesetTimeout(..0)hack,butimplementedinsuchawayastohaveamuchmorewell-definedandguaranteedordering:later,butassoonaspossible.

Jobs

Page 22: You Don't Know JS: Async & Performance

Let'simagineanAPIforschedulingJobs(directly,withouthacks),andcallitschedule(..).Consider:

console.log("A");

setTimeout(function(){

console.log("B");

},0);

//theoretical"JobAPI"

schedule(function(){

console.log("C");

schedule(function(){

console.log("D");

});

});

YoumightexpectthistoprintoutABCD,butinsteaditwouldprintoutACDB,becausetheJobshappenattheendofthecurrenteventlooptick,andthetimerfirestoscheduleforthenexteventlooptick(ifavailable!).

InChapter3,we'llseethattheasynchronousbehaviorofPromisesisbasedonJobs,soit'simportanttokeepclearhowthatrelatestoeventloopbehavior.

TheorderinwhichweexpressstatementsinourcodeisnotnecessarilythesameorderastheJSenginewillexecutethem.Thatmayseemlikequiteastrangeassertiontomake,sowe'lljustbrieflyexploreit.

Butbeforewedo,weshouldbecrystalclearonsomething:therules/grammarofthelanguage(seetheTypes&Grammartitleofthisbookseries)dictateaverypredictableandreliablebehaviorforstatementorderingfromtheprogrampointofview.Sowhatwe'reabouttodiscussarenotthingsyoushouldeverbeabletoobserveinyourJSprogram.

Warning:Ifyouareeverabletoobservecompilerstatementreorderinglikewe'reabouttoillustrate,that'dbeaclearviolationofthespecification,anditwouldunquestionablybeduetoabugintheJSengineinquestion--onewhichshouldpromptlybereportedandfixed!Butit'svastlymorecommonthatyoususpectsomethingcrazyishappeningintheJSengine,wheninfactit'sjustabug(probablya"racecondition"!)inyourowncode--solooktherefirst,andagainandagain.TheJSdebugger,usingbreakpointsandsteppingthroughcodelinebyline,willbeyourmostpowerfultoolforsniffingoutsuchbugsinyourcode.

Consider:

vara,b;

a=10;

b=30;

a=a+1;

b=b+1;

console.log(a+b);//42

Thiscodehasnoexpressedasynchronytoit(otherthantherareconsoleasyncI/Odiscussedearlier!),sothemostlikelyassumptionisthatitwouldprocesslinebylineintop-downfashion.

Butit'spossiblethattheJSengine,aftercompilingthiscode(yes,JSiscompiled--seetheScope&Closurestitleofthisbookseries!)mightfindopportunitiestorunyourcodefasterbyrearranging(safely)theorderofthesestatements.Essentially,aslongasyoucan'tobservethereordering,anything'sfairgame.

Forexample,theenginemightfindit'sfastertoactuallyexecutethecodelikethis:

StatementOrdering

Page 23: You Don't Know JS: Async & Performance

vara,b;

a=10;

a++;

b=30;

b++;

console.log(a+b);//42

Orthis:

vara,b;

a=11;

b=31;

console.log(a+b);//42

Oreven:

//because`a`and`b`aren'tusedanymore,wecan

//inlineanddon'tevenneedthem!

console.log(42);//42

Inallthesecases,theJSengineisperformingsafeoptimizationsduringitscompilation,astheendobservableresultwillbethesame.

Buthere'sascenariowherethesespecificoptimizationswouldbeunsafeandthuscouldn'tbeallowed(ofcourse,nottosaythatit'snotoptimizedatall):

vara,b;

a=10;

b=30;

//weneed`a`and`b`intheirpreincrementedstate!

console.log(a*b);//300

a=a+1;

b=b+1;

console.log(a+b);//42

Otherexampleswherethecompilerreorderingcouldcreateobservablesideeffects(andthusmustbedisallowed)wouldincludethingslikeanyfunctioncallwithsideeffects(evenandespeciallygetterfunctions),orES6Proxyobjects(seetheES6&Beyondtitleofthisbookseries).

Consider:

functionfoo(){

console.log(b);

return1;

}

vara,b,c;

//ES5.1getterliteralsyntax

c={

getbar(){

console.log(a);

return1;

}

};

Page 24: You Don't Know JS: Async & Performance

a=10;

b=30;

a+=foo();//30

b+=c.bar;//11

console.log(a+b);//42

Ifitweren'tfortheconsole.log(..)statementsinthissnippet(justusedasaconvenientformofobservablesideeffectfortheillustration),theJSenginewouldlikelyhavebeenfree,ifitwantedto(whoknowsifitwould!?),toreorderthecodeto:

//...

a=10+foo();

b=30+c.bar;

//...

WhileJSsemanticsthankfullyprotectusfromtheobservablenightmaresthatcompilerstatementreorderingwouldseemtobeindangerof,it'sstillimportanttounderstandjusthowtenuousalinkthereisbetweenthewaysourcecodeisauthored(intop-downfashion)andthewayitrunsaftercompilation.

Compilerstatementreorderingisalmostamicro-metaphorforconcurrencyandinteraction.Asageneralconcept,suchawarenesscanhelpyouunderstandasyncJScodeflowissuesbetter.

AJavaScriptprogramis(practically)alwaysbrokenupintotwoormorechunks,wherethefirstchunkrunsnowandthenextchunkrunslater,inresponsetoanevent.Eventhoughtheprogramisexecutedchunk-by-chunk,allofthemsharethesameaccesstotheprogramscopeandstate,soeachmodificationtostateismadeontopofthepreviousstate.

Wheneverthereareeventstorun,theeventlooprunsuntilthequeueisempty.Eachiterationoftheeventloopisa"tick."Userinteraction,IO,andtimersenqueueeventsontheeventqueue.

Atanygivenmoment,onlyoneeventcanbeprocessedfromthequeueatatime.Whileaneventisexecuting,itcandirectlyorindirectlycauseoneormoresubsequentevents.

Concurrencyiswhentwoormorechainsofeventsinterleaveovertime,suchthatfromahigh-levelperspective,theyappeartoberunningsimultaneously(eventhoughatanygivenmomentonlyoneeventisbeingprocessed).

It'softennecessarytodosomeformofinteractioncoordinationbetweentheseconcurrent"processes"(asdistinctfromoperatingsystemprocesses),forinstancetoensureorderingortoprevent"raceconditions."These"processes"canalsocooperatebybreakingthemselvesintosmallerchunksandtoallowother"process"interleaving.

Review

Page 25: You Don't Know JS: Async & Performance

InChapter1,weexploredtheterminologyandconceptsaroundasynchronousprogramminginJavaScript.Ourfocusisonunderstandingthesingle-threaded(one-at-a-time)eventloopqueuethatdrivesall"events"(asyncfunctioninvocations).Wealsoexploredvariouswaysthatconcurrencypatternsexplaintherelationships(ifany!)betweensimultaneouslyrunningchainsofevents,or"processes"(tasks,functioncalls,etc.).

AllourexamplesinChapter1usedthefunctionastheindividual,indivisibleunitofoperations,wherebyinsidethefunction,statementsruninpredictableorder(abovethecompilerlevel!),butatthefunction-orderinglevel,events(akaasyncfunctioninvocations)canhappeninavarietyoforders.

Inallthesecases,thefunctionisactingasa"callback,"becauseitservesasthetargetfortheeventloopto"callbackinto"theprogram,wheneverthatiteminthequeueisprocessed.

Asyounodoubthaveobserved,callbacksarebyfarthemostcommonwaythatasynchronyinJSprogramsisexpressedandmanaged.Indeed,thecallbackisthemostfundamentalasyncpatterninthelanguage.

CountlessJSprograms,evenverysophisticatedandcomplexones,havebeenwrittenuponnootherasyncfoundationthanthecallback(withofcoursetheconcurrencyinteractionpatternsweexploredinChapter1).ThecallbackfunctionistheasyncworkhorseforJavaScript,anditdoesitsjobrespectably.

Except...callbacksarenotwithouttheirshortcomings.Manydevelopersareexcitedbythepromise(punintended!)ofbetterasyncpatterns.Butit'simpossibletoeffectivelyuseanyabstractionifyoudon'tunderstandwhatit'sabstracting,andwhy.

Inthischapter,wewillexploreacoupleofthoseindepth,asmotivationforwhymoresophisticatedasyncpatterns(exploredinsubsequentchaptersofthisbook)arenecessaryanddesired.

Let'sgobacktotheasynccallbackexamplewestartedwithinChapter1,butletmeslightlymodifyittoillustrateapoint:

//A

ajax("..",function(..){

//C

});

//B

//Aand//Brepresentthefirsthalfoftheprogram(akathenow),and//Cmarksthesecondhalfoftheprogram(akathelater).Thefirsthalfexecutesrightaway,andthenthere'sa"pause"ofindeterminatelength.Atsomefuturemoment,iftheAjaxcallcompletes,thentheprogramwillpickupwhereitleftoff,andcontinuewiththesecondhalf.

Inotherwords,thecallbackfunctionwrapsorencapsulatesthecontinuationoftheprogram.

Let'smakethecodeevensimpler:

//A

setTimeout(function(){

//C

},1000);

//B

YouDon'tKnowJS:Async&Performance

Chapter2:Callbacks

Continuations

Page 26: You Don't Know JS: Async & Performance

Stopforamomentandaskyourselfhowyou'ddescribe(tosomeoneelselessinformedabouthowJSworks)thewaythatprogrambehaves.Goahead,tryitoutloud.It'sagoodexercisethatwillhelpmynextpointsmakemoresense.

Mostreadersjustnowprobablythoughtorsaidsomethingtotheeffectof:"DoA,thensetupatimeouttowait1,000milliseconds,thenoncethatfires,doC."Howclosewasyourrendition?

Youmighthavecaughtyourselfandself-editedto:"DoA,setupthetimeoutfor1,000milliseconds,thendoB,thenafterthetimeoutfires,doC."That'smoreaccuratethanthefirstversion.Canyouspotthedifference?

Eventhoughthesecondversionismoreaccurate,bothversionsaredeficientinexplainingthiscodeinawaythatmatchesourbrainstothecode,andthecodetotheJSengine.Thedisconnectisbothsubtleandmonumental,andisattheveryheartofunderstandingtheshortcomingsofcallbacksasasyncexpressionandmanagement.

Assoonasweintroduceasinglecontinuation(orseveraldozenasmanyprogramsdo!)intheformofacallbackfunction,wehaveallowedadivergencetoformbetweenhowourbrainsworkandthewaythecodewilloperate.Anytimethesetwodiverge(andthisisbyfarnottheonlyplacethathappens,asI'msureyouknow!),werunintotheinevitablefactthatourcodebecomeshardertounderstand,reasonabout,debug,andmaintain.

I'mprettysuremostofyoureadershaveheardsomeonesay(evenmadetheclaimyourself),"I'mamultitasker."Theeffectsoftryingtoactasamultitaskerrangefromhumorous(e.g.,thesillypatting-head-rubbing-stomachkids'game)tomundane(chewinggumwhilewalking)todownrightdangerous(textingwhiledriving).

Butarewemultitaskers?Canwereallydotwoconscious,intentionalactionsatonceandthink/reasonaboutbothofthematexactlythesamemoment?Doesourhighestlevelofbrainfunctionalityhaveparallelmultithreadinggoingon?

Theanswermaysurpriseyou:probablynot.

That'sjustnotreallyhowourbrainsappeartobesetup.We'remuchmoresingletaskersthanmanyofus(especiallyA-typepersonalities!)wouldliketoadmit.Wecanreallyonlythinkaboutonethingatanygiveninstant.

I'mnottalkingaboutallourinvoluntary,subconscious,automaticbrainfunctions,suchasheartbeating,breathing,andeyelidblinking.Thoseareallvitaltaskstooursustainedlife,butwedon'tintentionallyallocateanybrainpowertothem.Thankfully,whileweobsessaboutcheckingsocialnetworkfeedsforthe15thtimeinthreeminutes,ourbraincarriesoninthebackground(threads!)withallthoseimportanttasks.

We'reinsteadtalkingaboutwhatevertaskisattheforefrontofourmindsatthemoment.Forme,it'swritingthetextinthisbookrightnow.AmIdoinganyotherhigherlevelbrainfunctionatexactlythissamemoment?Nope,notreally.Igetdistractedquicklyandeasily--afewdozentimesintheselastcoupleofparagraphs!

Whenwefakemultitasking,suchastryingtotypesomethingatthesametimewe'retalkingtoafriendorfamilymemberonthephone,whatwe'reactuallymostlikelydoingisactingasfastcontextswitchers.Inotherwords,weswitchbackandforthbetweentwoormoretasksinrapidsuccession,simultaneouslyprogressingoneachtaskintiny,fastlittlechunks.Wedoitsofastthattotheoutsideworlditappearsasifwe'redoingthesethingsinparallel.

Doesthatsoundsuspiciouslylikeasynceventedconcurrency(likethesortthathappensinJS)toyou?!Ifnot,gobackandreadChapter1again!

Infact,onewayofsimplifying(i.e.,abusing)themassivelycomplexworldofneurologyintosomethingIcanremotelyhopetodiscusshereisthatourbrainsworkkindaliketheeventloopqueue.

Ifyouthinkabouteverysingleletter(orword)Itypeasasingleasyncevent,injustthissentencealonethereareseveraldozenopportunitiesformybraintobeinterruptedbysomeotherevent,suchasfrommysenses,orevenjustmyrandomthoughts.

Idon'tgetinterruptedandpulledtoanother"process"ateveryopportunitythatIcouldbe(thankfully--orthisbookwould

SequentialBrain

Page 27: You Don't Know JS: Async & Performance

neverbewritten!).ButithappensoftenenoughthatIfeelmyownbrainisnearlyconstantlyswitchingtovariousdifferentcontexts(aka"processes").Andthat'sanawfullotlikehowtheJSenginewouldprobablyfeel.

OK,soourbrainscanbethoughtofasoperatinginsingle-threadedeventloopqueuelikeways,ascantheJSengine.Thatsoundslikegoodmatch.

Butweneedtobemorenuancedthanthatinouranalysis.There'sabig,observabledifferencebetweenhowweplanvarioustasks,andhowourbrainsactuallyoperatethosetasks.

Again,backtothewritingofthistextasmymetaphor.Myroughmentaloutlineplanhereistokeepwritingandwriting,goingsequentiallythroughasetofpointsIhaveorderedinmythoughts.Idon'tplantohaveanyinterruptionsornonlinearactivityinthiswriting.Butyet,mybrainisneverthelessswitchingaroundallthetime.

Eventhoughatanoperationallevelourbrainsareasyncevented,weseemtoplanouttasksinasequential,synchronousway."Ineedtogotothestore,thenbuysomemilk,thendropoffmydrycleaning."

You'llnoticethatthishigherlevelthinking(planning)doesn'tseemveryasynceventedinitsformulation.Infact,it'skindofrareforustodeliberatelythinksolelyintermsofevents.Instead,weplanthingsoutcarefully,sequentially(AthenBthenC),andweassumetoanextentasortoftemporalblockingthatforcesBtowaitonA,andCtowaitonB.

Whenadeveloperwritescode,theyareplanningoutasetofactionstooccur.Ifthey'reanygoodatbeingadeveloper,they'recarefullyplanningitout."Ineedtosetztothevalueofx,andthenxtothevalueofy,"andsoforth.

Whenwewriteoutsynchronouscode,statementbystatement,itworksalotlikeourerrandsto-dolist:

//swap`x`and`y`(viatempvariable`z`)

z=x;

x=y;

y=z;

Thesethreeassignmentstatementsaresynchronous,sox=ywaitsforz=xtofinish,andy=zinturnwaitsforx=ytofinish.Anotherwayofsayingitisthatthesethreestatementsaretemporallyboundtoexecuteinacertainorder,onerightaftertheother.Thankfully,wedon'tneedtobebotheredwithanyasynceventeddetailshere.Ifwedid,thecodegetsalotmorecomplex,quickly!

Soifsynchronousbrainplanningmapswelltosynchronouscodestatements,howwelldoourbrainsdoatplanningoutasynchronouscode?

Itturnsoutthathowweexpressasynchrony(withcallbacks)inourcodedoesn'tmapverywellatalltothatsynchronousbrainplanningbehavior.

Canyouactuallyimaginehavingalineofthinkingthatplansoutyourto-doerrandslikethis?

"Ineedtogotothestore,butonthewayI'msureI'llgetaphonecall,so'Hi,Mom',andwhileshestartstalking,I'llbelookingupthestoreaddressonGPS,butthat'lltakeasecondtoload,soI'llturndowntheradiosoIcanhearMombetter,thenI'llrealizeIforgottoputonajacketandit'scoldoutside,butnomatter,keepdrivingandtalkingtoMom,andthentheseatbeltdingremindsmetobuckleup,so'Yes,Mom,Iamwearingmyseatbelt,Ialwaysdo!'.Ah,finallytheGPSgotthedirections,now..."

Asridiculousasthatsoundsasaformulationforhowweplanourdayoutandthinkaboutwhattodoandinwhatorder,nonethelessit'sexactlyhowourbrainsoperateatafunctionallevel.Remember,that'snotmultitasking,it'sjustfastcontextswitching.

Thereasonit'sdifficultforusasdeveloperstowriteasynceventedcode,especiallywhenallwehaveisthecallbacktodoit,isthatstreamofconsciousnessthinking/planningisunnaturalformostofus.

DoingVersusPlanning

Page 28: You Don't Know JS: Async & Performance

Wethinkinstep-by-stepterms,butthetools(callbacks)availabletousincodearenotexpressedinastep-by-stepfashiononcewemovefromsynchronoustoasynchronous.

Andthatiswhyit'ssohardtoaccuratelyauthorandreasonaboutasyncJScodewithcallbacks:becauseit'snothowourbrainplanningworks.

Note:Theonlythingworsethannotknowingwhysomecodebreaksisnotknowingwhyitworkedinthefirstplace!It'stheclassic"houseofcards"mentality:"itworks,butnotsurewhy,sonobodytouchit!"Youmayhaveheard,"Hellisotherpeople"(Sartre),andtheprogrammermemetwist,"Hellisotherpeople'scode."Ibelievetruly:"Hellisnotunderstandingmyowncode."Andcallbacksareonemainculprit.

Consider:

listen("click",functionhandler(evt){

setTimeout(functionrequest(){

ajax("http://some.url.1",functionresponse(text){

if(text=="hello"){

handler();

}

elseif(text=="world"){

request();

}

});

},500);

});

There'sagoodchancecodelikethatisrecognizabletoyou.We'vegotachainofthreefunctionsnestedtogether,eachonerepresentingastepinanasynchronousseries(task,"process").

Thiskindofcodeisoftencalled"callbackhell,"andsometimesalsoreferredtoasthe"pyramidofdoom"(foritssideways-facingtriangularshapeduetothenestedindentation).

But"callbackhell"actuallyhasalmostnothingtodowiththenesting/indentation.It'safardeeperproblemthanthat.We'llseehowandwhyaswecontinuethroughtherestofthischapter.

First,we'rewaitingforthe"click"event,thenwe'rewaitingforthetimertofire,thenwe'rewaitingfortheAjaxresponsetocomeback,atwhichpointitmightdoitallagain.

Atfirstglance,thiscodemayseemtomapitsasynchronynaturallytosequentialbrainplanning.

First(now),we:

listen("..",functionhandler(..){

//..

});

Thenlater,we:

setTimeout(functionrequest(..){

//..

},500);

Thenstilllater,we:

ajax("..",functionresponse(..){

//..

});

Nested/ChainedCallbacks

Page 29: You Don't Know JS: Async & Performance

Andfinally(mostlater),we:

if(..){

//..

}

else..

Butthere'sseveralproblemswithreasoningaboutthiscodelinearlyinsuchafashion.

First,it'sanaccidentoftheexamplethatourstepsareonsubsequentlines(1,2,3,and4...).InrealasyncJSprograms,there'softenalotmorenoiseclutteringthingsup,noisethatwehavetodeftlymaneuverpastinourbrainsaswejumpfromonefunctiontothenext.Understandingtheasyncflowinsuchcallback-ladencodeisnotimpossible,butit'scertainlynotnaturaloreasy,evenwithlotsofpractice.

Butalso,there'ssomethingdeeperwrong,whichisn'tevidentjustinthatcodeexample.Letmemakeupanotherscenario(pseudocode-ish)toillustrateit:

doA(function(){

doB();

doC(function(){

doD();

})

doE();

});

doF();

Whiletheexperiencedamongyouwillcorrectlyidentifythetrueorderofoperationshere,I'mbettingitismorethanalittleconfusingatfirstglance,andtakessomeconcertedmentalcyclestoarriveat.Theoperationswillhappeninthisorder:

doA()

doF()

doB()

doC()

doE()

doD()

Didyougetthatrighttheveryfirsttimeyouglancedatthecode?

OK,someofyouarethinkingIwasunfairinmyfunctionnaming,tointentionallyleadyouastray.IswearIwasjustnamingintop-downappearanceorder.Butletmetryagain:

doA(function(){

doC();

doD(function(){

doF();

})

doE();

});

doB();

Now,I'venamedthemalphabeticallyinorderofactualexecution.ButIstillbet,evenwithexperiencenowinthisscenario,tracingthroughtheA->B->C->D->E->Forderdoesn'tcomenaturaltomanyifanyofyoureaders.Certainlyyoureyesdoanawfullotofjumpingupanddownthecodesnippet,right?

Page 30: You Don't Know JS: Async & Performance

Butevenifthatallcomesnaturaltoyou,there'sstillonemorehazardthatcouldwreakhavoc.Canyouspotwhatitis?

WhatifdoA(..)ordoD(..)aren'tactuallyasync,thewayweobviouslyassumedthemtobe?Uhoh,nowtheorderisdifferent.Ifthey'rebothsync(andmaybeonlysometimes,dependingontheconditionsoftheprogramatthetime),theorderisnowA->C->D->F->E->B.

ThatsoundyoujustheardfaintlyinthebackgroundisthesighsofthousandsofJSdeveloperswhojusthadaface-in-handsmoment.

Isnestingtheproblem?Isthatwhatmakesitsohardtotracetheasyncflow?That'spartofit,certainly.

Butletmerewritethepreviousnestedevent/timeout/Ajaxexamplewithoutusingnesting:

listen("click",handler);

functionhandler(){

setTimeout(request,500);

}

functionrequest(){

ajax("http://some.url.1",response);

}

functionresponse(text){

if(text=="hello"){

handler();

}

elseif(text=="world"){

request();

}

}

Thisformulationofthecodeisnothardlyasrecognizableashavingthenesting/indentationwoesofitspreviousform,andyetit'severybitassusceptibleto"callbackhell."Why?

Aswegotolinearly(sequentially)reasonaboutthiscode,wehavetoskipfromonefunction,tothenext,tothenext,andbounceallaroundthecodebaseto"see"thesequenceflow.Andremember,thisissimplifiedcodeinsortofbest-casefashion.WeallknowthatrealasyncJSprogramcodebasesareoftenfantasticallymorejumbled,whichmakessuchreasoningordersofmagnitudemoredifficult.

Anotherthingtonotice:togetsteps2,3,and4linkedtogethersotheyhappeninsuccession,theonlyaffordancecallbacksalonegivesusistohardcodestep2intostep1,step3intostep2,step4intostep3,andsoon.Thehardcodingisn'tnecessarilyabadthing,ifitreallyisafixedconditionthatstep2shouldalwaysleadtostep3.

Butthehardcodingdefinitelymakesthecodeabitmorebrittle,asitdoesn'taccountforanythinggoingwrongthatmightcauseadeviationintheprogressionofsteps.Forexample,ifstep2fails,step3nevergetsreached,nordoesstep2retry,ormovetoanalternateerrorhandlingflow,andsoon.

Alloftheseissuesarethingsyoucanmanuallyhardcodeintoeachstep,butthatcodeisoftenveryrepetitiveandnotreusableinotherstepsorinotherasyncflowsinyourprogram.

Eventhoughourbrainsmightplanoutaseriesoftasksinasequentialtypeofway(this,thenthis,thenthis),theeventednatureofourbrainoperationmakesrecovery/retry/forkingofflowcontrolalmosteffortless.Ifyou'reoutrunningerrands,andyourealizeyouleftashoppinglistathome,itdoesn'tendthedaybecauseyoudidn'tplanthataheadoftime.Yourbrainroutesaroundthishiccupeasily:yougohome,getthelist,thenheadrightbackouttothestore.

Butthebrittlenatureofmanuallyhardcodedcallbacks(evenwithhardcodederrorhandling)isoftenfarlessgraceful.Onceyouendupspecifying(akapre-planning)allthevariouseventualities/paths,thecodebecomessoconvolutedthatit'shardtoevermaintainorupdateit.

Thatiswhat"callbackhell"isallabout!Thenesting/indentationarebasicallyasideshow,aredherring.

Page 31: You Don't Know JS: Async & Performance

Andasifallthat'snotenough,wehaven'teventouchedwhathappenswhentwoormorechainsofthesecallbackcontinuationsarehappeningsimultaneously,orwhenthethirdstepbranchesoutinto"parallel"callbackswithgatesorlatches,or...OMG,mybrainhurts,howaboutyours!?

Areyoucatchingthenotionherethatoursequential,blockingbrainplanningbehaviorsjustdon'tmapwellontocallback-orientedasynccode?That'sthefirstmajordeficiencytoarticulateaboutcallbacks:theyexpressasynchronyincodeinwaysourbrainshavetofightjusttokeepinsyncwith(punintended!).

Themismatchbetweensequentialbrainplanningandcallback-drivenasyncJScodeisonlypartoftheproblemwithcallbacks.There'ssomethingmuchdeepertobeconcernedabout.

Let'sonceagainrevisitthenotionofacallbackfunctionasthecontinuation(akathesecondhalf)ofourprogram:

//A

ajax("..",function(..){

//C

});

//B

//Aand//Bhappennow,underthedirectcontrolofthemainJSprogram.But//Cgetsdeferredtohappenlater,andunderthecontrolofanotherparty--inthiscase,theajax(..)function.Inabasicsense,thatsortofhand-offofcontroldoesn'tregularlycauselotsofproblemsforprograms.

Butdon'tbefooledbyitsinfrequencythatthiscontrolswitchisn'tabigdeal.Infact,it'soneoftheworst(andyetmostsubtle)problemsaboutcallback-drivendesign.Itrevolvesaroundtheideathatsometimesajax(..)(i.e.,the"party"youhandyourcallbackcontinuationto)isnotafunctionthatyouwrote,orthatyoudirectlycontrol.Manytimesit'sautilityprovidedbysomethirdparty.

Wecallthis"inversionofcontrol,"whenyoutakepartofyourprogramandgiveovercontrolofitsexecutiontoanotherthirdparty.There'sanunspoken"contract"thatexistsbetweenyourcodeandthethird-partyutility--asetofthingsyouexpecttobemaintained.

Itmightnotbeterriblyobviouswhythisissuchabigdeal.Letmeconstructanexaggeratedscenariotoillustratethehazardsoftrustatplay.

Imagineyou'readevelopertaskedwithbuildingoutanecommercecheckoutsystemforasitethatsellsexpensiveTVs.Youalreadyhaveallthevariouspagesofthecheckoutsystembuiltoutjustfine.Onthelastpage,whentheuserclicks"confirm"tobuytheTV,youneedtocallathird-partyfunction(providedsaybysomeanalyticstrackingcompany)sothatthesalecanbetracked.

Younoticethatthey'veprovidedwhatlookslikeanasynctrackingutility,probablyforthesakeofperformancebestpractices,whichmeansyouneedtopassinacallbackfunction.Inthiscontinuationthatyoupassin,youwillhavethefinalcodethatchargesthecustomer'screditcardanddisplaysthethankyoupage.

Thiscodemightlooklike:

analytics.trackPurchase(purchaseData,function(){

chargeCreditCard();

displayThankyouPage();

});

Easyenough,right?Youwritethecode,testit,everythingworks,andyoudeploytoproduction.Everyone'shappy!

TrustIssues

TaleofFiveCallbacks

Page 32: You Don't Know JS: Async & Performance

Sixmonthsgobyandnoissues.You'vealmostforgottenyouevenwrotethatcode.Onemorning,you'reatacoffeeshopbeforework,casuallyenjoyingyourlatte,whenyougetapanickedcallfromyourbossinsistingyoudropthecoffeeandrushintoworkrightaway.

Whenyouarrive,youfindoutthatahigh-profilecustomerhashadhiscreditcardchargedfivetimesforthesameTV,andhe'sunderstandablyupset.Customerservicehasalreadyissuedanapologyandprocessedarefund.Butyourbossdemandstoknowhowthiscouldpossiblyhavehappened."Don'twehavetestsforstufflikethis!?"

Youdon'tevenrememberthecodeyouwrote.Butyoudigbackinandstarttryingtofindoutwhatcouldhavegoneawry.

Afterdiggingthroughsomelogs,youcometotheconclusionthattheonlyexplanationisthattheanalyticsutilitysomehow,forsomereason,calledyourcallbackfivetimesinsteadofonce.Nothingintheirdocumentationmentionsanythingaboutthis.

Frustrated,youcontactcustomersupport,whoofcourseisasastonishedasyouare.Theyagreetoescalateittotheirdevelopers,andpromisetogetbacktoyou.Thenextday,youreceivealengthyemailexplainingwhattheyfound,whichyoupromptlyforwardtoyourboss.

Apparently,thedevelopersattheanalyticscompanyhadbeenworkingonsomeexperimentalcodethat,undercertainconditions,wouldretrytheprovidedcallbackoncepersecond,forfiveseconds,beforefailingwithatimeout.Theyhadneverintendedtopushthatintoproduction,butsomehowtheydid,andthey'retotallyembarrassedandapologetic.Theygointoplentyofdetailabouthowthey'veidentifiedthebreakdownandwhatthey'lldotoensureitneverhappensagain.Yadda,yadda.

What'snext?

Youtalkitoverwithyourboss,buthe'snotfeelingparticularlycomfortablewiththestateofthings.Heinsists,andyoureluctantlyagree,thatyoucan'ttrustthemanymore(that'swhatbityou),andthatyou'llneedtofigureouthowtoprotectthecheckoutcodefromsuchavulnerabilityagain.

Aftersometinkering,youimplementsomesimpleadhoccodelikethefollowing,whichtheteamseemshappywith:

vartracked=false;

analytics.trackPurchase(purchaseData,function(){

if(!tracked){

tracked=true;

chargeCreditCard();

displayThankyouPage();

}

});

Note:ThisshouldlookfamiliartoyoufromChapter1,becausewe'reessentiallycreatingalatchtohandleiftherehappentobemultipleconcurrentinvocationsofourcallback.

ButthenoneofyourQAengineersasks,"whathappensiftheynevercallthecallback?"Oops.Neitherofyouhadthoughtaboutthat.

Youbegintochasedowntherabbithole,andthinkofallthepossiblethingsthatcouldgowrongwiththemcallingyourcallback.Here'sroughlythelistyoucomeupwithofwaystheanalyticsutilitycouldmisbehave:

Callthecallbacktooearly(beforeit'sbeentracked)Callthecallbacktoolate(ornever)Callthecallbacktoofewortoomanytimes(liketheproblemyouencountered!)Failtopassalonganynecessaryenvironment/parameterstoyourcallbackSwallowanyerrors/exceptionsthatmayhappen...

Thatshouldfeellikeatroublinglist,becauseitis.You'reprobablyslowlystartingtorealizethatyou'regoingtohaveto

Page 33: You Don't Know JS: Async & Performance

inventanawfullotofadhoclogicineachandeverysinglecallbackthat'spassedtoautilityyou'renotpositiveyoucantrust.

Nowyourealizeabitmorecompletelyjusthowhellish"callbackhell"is.

SomeofyoumaybeskepticalatthispointwhetherthisisasbigadealasI'mmakingitouttobe.Perhapsyoudon'tinteractwithtrulythird-partyutilitiesmuchifatall.PerhapsyouuseversionedAPIsorself-hostsuchlibraries,sothatitsbehaviorcan'tbechangedoutfromunderneathyou.

So,contemplatethis:canyouevenreallytrustutilitiesthatyoudotheoreticallycontrol(inyourowncodebase)?

Thinkofitthisway:mostofusagreethatatleasttosomeextentweshouldbuildourowninternalfunctionswithsomedefensivechecksontheinputparameters,toreduce/preventunexpectedissues.

Overlytrustingofinput:

functionaddNumbers(x,y){

//+isoverloadedwithcoerciontoalsobe

//stringconcatenation,sothisoperation

//isn'tstrictlysafedependingonwhat's

//passedin.

returnx+y;

}

addNumbers(21,21);//42

addNumbers(21,"21");//"2121"

Defensiveagainstuntrustedinput:

functionaddNumbers(x,y){

//ensurenumericalinput

if(typeofx!="number"||typeofy!="number"){

throwError("Badparameters");

}

//ifwegethere,+willsafelydonumericaddition

returnx+y;

}

addNumbers(21,21);//42

addNumbers(21,"21");//Error:"Badparameters"

Orperhapsstillsafebutfriendlier:

functionaddNumbers(x,y){

//ensurenumericalinput

x=Number(x);

y=Number(y);

//+willsafelydonumericaddition

returnx+y;

}

addNumbers(21,21);//42

addNumbers(21,"21");//42

Howeveryougoaboutit,thesesortsofchecks/normalizationsarefairlycommononfunctioninputs,evenwithcodewetheoreticallyentirelytrust.Inacrudesortofway,it'sliketheprogrammingequivalentofthegeopoliticalprincipleof"TrustButVerify."

So,doesn'titstandtoreasonthatweshoulddothesamethingaboutcompositionofasyncfunctioncallbacks,notjustwith

NotJustOthers'Code

Page 34: You Don't Know JS: Async & Performance

trulyexternalcodebutevenwithcodeweknowisgenerally"underourowncontrol"?Ofcourseweshould.

Butcallbacksdon'treallyofferanythingtoassistus.Wehavetoconstructallthatmachineryourselves,anditoftenendsupbeingalotofboilerplate/overheadthatwerepeatforeverysingleasynccallback.

Themosttroublesomeproblemwithcallbacksisinversionofcontrolleadingtoacompletebreakdownalongallthosetrustlines.

Ifyouhavecodethatusescallbacks,especiallybutnotexclusivelywiththird-partyutilities,andyou'renotalreadyapplyingsomesortofmitigationlogicforalltheseinversionofcontroltrustissues,yourcodehasbugsinitrightnoweventhoughtheymaynothavebittenyouyet.Latentbugsarestillbugs.

Hellindeed.

Thereareseveralvariationsofcallbackdesignthathaveattemptedtoaddresssome(notall!)ofthetrustissueswe'vejustlookedat.It'savaliant,butdoomed,efforttosavethecallbackpatternfromimplodingonitself.

Forexample,regardingmoregracefulerrorhandling,someAPIdesignsprovideforsplitcallbacks(oneforthesuccessnotification,onefortheerrornotification):

functionsuccess(data){

console.log(data);

}

functionfailure(err){

console.error(err);

}

ajax("http://some.url.1",success,failure);

InAPIsofthisdesign,oftenthefailure()errorhandlerisoptional,andifnotprovideditwillbeassumedyouwanttheerrorsswallowed.Ugh.

Note:Thissplit-callbackdesigniswhattheES6PromiseAPIuses.We'llcoverES6Promisesinmuchmoredetailinthenextchapter.

Anothercommoncallbackpatterniscalled"error-firststyle"(sometimescalled"Nodestyle,"asit'salsotheconventionusedacrossnearlyallNode.jsAPIs),wherethefirstargumentofasinglecallbackisreservedforanerrorobject(ifany).Ifsuccess,thisargumentwillbeempty/falsy(andanysubsequentargumentswillbethesuccessdata),butifanerrorresultisbeingsignaled,thefirstargumentisset/truthy(andusuallynothingelseispassed):

functionresponse(err,data){

//error?

if(err){

console.error(err);

}

//otherwise,assumesuccess

else{

console.log(data);

}

}

ajax("http://some.url.1",response);

Inbothofthesecases,severalthingsshouldbeobserved.

First,ithasnotreallyresolvedthemajorityoftrustissueslikeitmayappear.There'snothingabouteithercallbackthatpreventsorfiltersunwantedrepeatedinvocations.Moreover,thingsareworsenow,becauseyoumaygetbothsuccessand

TryingtoSaveCallbacks

Page 35: You Don't Know JS: Async & Performance

errorsignals,orneither,andyoustillhavetocodearoundeitherofthoseconditions.

Also,don'tmissthefactthatwhileit'sastandardpatternyoucanemploy,it'sdefinitelymoreverboseandboilerplate-ishwithoutmuchreuse,soyou'regoingtogetwearyoftypingallthatoutforeverysinglecallbackinyourapplication.

Whataboutthetrustissueofneverbeingcalled?Ifthisisaconcern(anditprobablyshouldbe!),youlikelywillneedtosetupatimeoutthatcancelstheevent.Youcouldmakeautility(proof-of-conceptonlyshown)tohelpyouwiththat:

functiontimeoutify(fn,delay){

varintv=setTimeout(function(){

intv=null;

fn(newError("Timeout!"));

},delay)

;

returnfunction(){

//timeouthasn'thappenedyet?

if(intv){

clearTimeout(intv);

fn.apply(this,arguments);

}

};

}

Here'showyouuseit:

//using"error-firststyle"callbackdesign

functionfoo(err,data){

if(err){

console.error(err);

}

else{

console.log(data);

}

}

ajax("http://some.url.1",timeoutify(foo,500));

Anothertrustissueisbeingcalled"tooearly."Inapplication-specificterms,thismayactuallyinvolvebeingcalledbeforesomecriticaltaskiscomplete.Butmoregenerally,theproblemisevidentinutilitiesthatcaneitherinvokethecallbackyouprovidenow(synchronously),orlater(asynchronously).

Thisnondeterminismaroundthesync-or-asyncbehaviorisalmostalwaysgoingtoleadtoverydifficulttotrackdownbugs.Insomecircles,thefictionalinsanity-inducingmonsternamedZalgoisusedtodescribethesync/asyncnightmares."Don'treleaseZalgo!"isacommoncry,anditleadstoverysoundadvice:alwaysinvokecallbacksasynchronously,evenifthat's"rightaway"onthenextturnoftheeventloop,sothatallcallbacksarepredictablyasync.

Note:FormoreinformationonZalgo,seeOrenGolan's"Don'tReleaseZalgo!"(https://github.com/oren/oren.github.io/blob/master/posts/zalgo.md)andIsaacZ.Schlueter's"DesigningAPIsforAsynchrony"(http://blog.izs.me/post/59142742143/designing-apis-for-asynchrony).

Consider:

functionresult(data){

console.log(a);

}

vara=0;

ajax("..pre-cached-url..",result);

a++;

Willthiscodeprint0(synccallbackinvocation)or1(asynccallbackinvocation)?Depends...ontheconditions.

Page 36: You Don't Know JS: Async & Performance

YoucanseejusthowquicklytheunpredictabilityofZalgocanthreatenanyJSprogram.Sothesilly-sounding"neverreleaseZalgo"isactuallyincrediblycommonandsolidadvice.Alwaysbeasyncing.

Whatifyoudon'tknowwhethertheAPIinquestionwillalwaysexecuteasync?Youcouldinventautilitylikethisasyncify(..)proof-of-concept:

functionasyncify(fn){

varorig_fn=fn,

intv=setTimeout(function(){

intv=null;

if(fn)fn();

},0)

;

fn=null;

returnfunction(){

//firingtooquickly,before`intv`timerhasfiredto

//indicateasyncturnhaspassed?

if(intv){

fn=orig_fn.bind.apply(

orig_fn,

//addthewrapper's`this`tothe`bind(..)`

//callparameters,aswellascurryingany

//passedinparameters

[this].concat([].slice.call(arguments))

);

}

//alreadyasync

else{

//invokeoriginalfunction

orig_fn.apply(this,arguments);

}

};

}

Youuseasyncify(..)likethis:

functionresult(data){

console.log(a);

}

vara=0;

ajax("..pre-cached-url..",asyncify(result));

a++;

WhethertheAjaxrequestisinthecacheandresolvestotrytocallthecallbackrightaway,ormustbefetchedoverthewireandthuscompletelaterasynchronously,thiscodewillalwaysoutput1insteadof0--result(..)cannothelpbutbeinvokedasynchronously,whichmeansthea++hasachancetorunbeforeresult(..)does.

Yay,anothertrustissued"solved"!Butit'sinefficient,andyetagainmorebloatedboilerplatetoweighyourprojectdown.

That'sjustthestory,overandoveragain,withcallbacks.Theycandoprettymuchanythingyouwant,butyouhavetobewillingtoworkhardtogetit,andoftentimesthiseffortismuchmorethanyoucanorshouldspendonsuchcodereasoning.

Youmightfindyourselfwishingforbuilt-inAPIsorotherlanguagemechanicstoaddresstheseissues.FinallyES6hasarrivedonthescenewithsomegreatanswers,sokeepreading!

CallbacksarethefundamentalunitofasynchronyinJS.Butthey'renotenoughfortheevolvinglandscapeofasyncprogrammingasJSmatures.

First,ourbrainsplanthingsoutinsequential,blocking,single-threadedsemanticways,butcallbacksexpress

Review

Page 37: You Don't Know JS: Async & Performance

asynchronousflowinarathernonlinear,nonsequentialway,whichmakesreasoningproperlyaboutsuchcodemuchharder.Badtoreasonaboutcodeisbadcodethatleadstobadbugs.

Weneedawaytoexpressasynchronyinamoresynchronous,sequential,blockingmanner,justlikeourbrainsdo.

Second,andmoreimportantly,callbackssufferfrominversionofcontrolinthattheyimplicitlygivecontrolovertoanotherparty(oftenathird-partyutilitynotinyourcontrol!)toinvokethecontinuationofyourprogram.Thiscontroltransferleadsustoatroublinglistoftrustissues,suchaswhetherthecallbackiscalledmoretimesthanweexpect.

Inventingadhoclogictosolvethesetrustissuesispossible,butit'smoredifficultthanitshouldbe,anditproducesclunkierandhardertomaintaincode,aswellascodethatislikelyinsufficientlyprotectedfromthesehazardsuntilyougetvisiblybittenbythebugs.

Weneedageneralizedsolutiontoallofthetrustissues,onethatcanbereusedforasmanycallbacksaswecreatewithoutalltheextraboilerplateoverhead.

Weneedsomethingbetterthancallbacks.They'veserveduswelltothispoint,butthefutureofJavaScriptdemandsmoresophisticatedandcapableasyncpatterns.Thesubsequentchaptersinthisbookwilldiveintothoseemergingevolutions.

Page 38: You Don't Know JS: Async & Performance

InChapter2,weidentifiedtwomajorcategoriesofdeficiencieswithusingcallbackstoexpressprogramasynchronyandmanageconcurrency:lackofsequentialityandlackoftrustability.Nowthatweunderstandtheproblemsmoreintimately,it'stimeweturnourattentiontopatternsthatcanaddressthem.

Theissuewewanttoaddressfirstistheinversionofcontrol,thetrustthatissofragilelyheldandsoeasilylost.

Recallthatwewrapupthecontinuationofourprograminacallbackfunction,andhandthatcallbackovertoanotherparty(potentiallyevenexternalcode)andjustcrossourfingersthatitwilldotherightthingwiththeinvocationofthecallback.

Wedothisbecausewewanttosay,"here'swhathappenslater,afterthecurrentstepfinishes."

Butwhatifwecoulduninvertthatinversionofcontrol?Whatifinsteadofhandingthecontinuationofourprogramtoanotherparty,wecouldexpectittoreturnusacapabilitytoknowwhenitstaskfinishes,andthenourcodecoulddecidewhattodonext?

ThisparadigmiscalledPromises.

PromisesarestartingtotaketheJSworldbystorm,asdevelopersandspecificationwritersalikedesperatelyseektountangletheinsanityofcallbackhellintheircode/design.Infact,mostnewasyncAPIsbeingaddedtoJS/DOMplatformarebeingbuiltonPromises.Soit'sprobablyagoodideatodiginandlearnthem,don'tyouthink!?

Note:Theword"immediately"willbeusedfrequentlyinthischapter,generallytorefertosomePromiseresolutionaction.However,inessentiallyallcases,"immediately"meansintermsoftheJobqueuebehavior(seeChapter1),notinthestrictlysynchronousnowsense.

Whendevelopersdecidetolearnanewtechnologyorpattern,usuallytheirfirststepis"Showmethecode!"It'squitenaturalforustojustjumpinfeetfirstandlearnaswego.

ButitturnsoutthatsomeabstractionsgetlostontheAPIsalone.Promisesareoneofthosetoolswhereitcanbepainfullyobviousfromhowsomeoneusesitwhethertheyunderstandwhatit'sforandaboutversusjustlearningandusingtheAPI.

SobeforeIshowthePromisecode,IwanttofullyexplainwhataPromisereallyisconceptually.IhopethiswillthenguideyoubetterasyouexploreintegratingPromisetheoryintoyourownasyncflow.

Withthatinmind,let'slookattwodifferentanalogiesforwhataPromiseis.

Imaginethisscenario:Iwalkuptothecounteratafast-foodrestaurant,andplaceanorderforacheeseburger.Ihandthecashier$1.47.Byplacingmyorderandpayingforit,I'vemadearequestforavalueback(thecheeseburger).I'vestartedatransaction.

Butoften,thechesseburgerisnotimmediatelyavailableforme.Thecashierhandsmesomethinginplaceofmycheeseburger:areceiptwithanordernumberonit.ThisordernumberisanIOU("Ioweyou")promisethatensuresthateventually,Ishouldreceivemycheeseburger.

SoIholdontomyreceiptandordernumber.Iknowitrepresentsmyfuturecheeseburger,soIdon'tneedtoworryaboutitanymore--asidefrombeinghungry!

YouDon'tKnowJS:Async&Performance

Chapter3:Promises

WhatIsaPromise?

FutureValue

Page 39: You Don't Know JS: Async & Performance

WhileIwait,Icandootherthings,likesendatextmessagetoafriendthatsays,"Hey,canyoucomejoinmeforlunch?I'mgoingtoeatacheeseburger."

Iamreasoningaboutmyfuturecheeseburgeralready,eventhoughIdon'thaveitinmyhandsyet.Mybrainisabletodothisbecauseit'streatingtheordernumberasaplaceholderforthecheeseburger.Theplaceholderessentiallymakesthevaluetimeindependent.It'safuturevalue.

Eventually,Ihear,"Order113!"andIgleefullywalkbackuptothecounterwithreceiptinhand.Ihandmyreceipttothecashier,andItakemycheeseburgerinreturn.

Inotherwords,oncemyfuturevaluewasready,Iexchangedmyvalue-promiseforthevalueitself.

Butthere'sanotherpossibleoutcome.Theycallmyordernumber,butwhenIgotoretrievemycheeseburger,thecashierregretfullyinformsme,"I'msorry,butweappeartobealloutofcheeseburgers."Settingasidethecustomerfrustrationofthisscenarioforamoment,wecanseeanimportantcharacteristicoffuturevalues:theycaneitherindicateasuccessorfailure.

EverytimeIorderacheeseburger,IknowthatI'lleithergetacheeseburgereventually,orI'llgetthesadnewsofthecheeseburgershortage,andI'llhavetofigureoutsomethingelsetoeatforlunch.

Note:Incode,thingsarenotquiteassimple,becausemetaphoricallytheordernumbermayneverbecalled,inwhichcasewe'releftindefinitelyinanunresolvedstate.We'llcomebacktodealingwiththatcaselater.

Thisallmightsoundtoomentallyabstracttoapplytoyourcode.Solet'sbemoreconcrete.

However,beforewecanintroducehowPromisesworkinthisfashion,we'regoingtoderiveincodethatwealreadyunderstand--callbacks!--howtohandlethesefuturevalues.

Whenyouwritecodetoreasonaboutavalue,suchasperformingmathonanumber,whetheryourealizeitornot,you'vebeenassumingsomethingveryfundamentalaboutthatvalue,whichisthatit'saconcretenowvaluealready:

varx,y=2;

console.log(x+y);//NaN<--because`x`isn'tsetyet

Thex+yoperationassumesbothxandyarealreadyset.Intermswe'llexpoundonshortly,weassumethexandyvaluesarealreadyresolved.

Itwouldbenonsensetoexpectthatthe+operatorbyitselfwouldsomehowbemagicallycapableofdetectingandwaitingarounduntilbothxandyareresolved(akaready),onlythentodotheoperation.Thatwouldcausechaosintheprogramifdifferentstatementsfinishednowandothersfinishedlater,right?

Howcouldyoupossiblyreasonabouttherelationshipsbetweentwostatementsifeitherone(orboth)ofthemmightnotbefinishedyet?Ifstatement2reliesonstatement1beingfinished,therearejusttwooutcomes:eitherstatement1finishedrightnowandeverythingproceedsfine,orstatement1didn'tfinishyet,andthusstatement2isgoingtofail.

IfthissortofthingsoundsfamiliarfromChapter1,good!

Let'sgobacktoourx+ymathoperation.Imagineiftherewasawaytosay,"Addxandy,butifeitherofthemisn'treadyyet,justwaituntiltheyare.Addthemassoonasyoucan."

Yourbrainmighthavejustjumpedtocallbacks.OK,so...

functionadd(getX,getY,cb){

varx,y;

getX(function(xVal){

ValuesNowandLater

Page 40: You Don't Know JS: Async & Performance

x=xVal;

//bothareready?

if(y!=undefined){

cb(x+y);//sendalongsum

}

});

getY(function(yVal){

y=yVal;

//bothareready?

if(x!=undefined){

cb(x+y);//sendalongsum

}

});

}

//`fetchX()`and`fetchY()`aresyncorasync

//functions

add(fetchX,fetchY,function(sum){

console.log(sum);//thatwaseasy,huh?

});

Takejustamomenttoletthebeauty(orlackthereof)ofthatsnippetsinkin(whistlespatiently).

Whiletheuglinessisundeniable,there'ssomethingveryimportanttonoticeaboutthisasyncpattern.

Inthatsnippet,wetreatedxandyasfuturevalues,andweexpressanoperationadd(..)that(fromtheoutside)doesnotcarewhetherxoryorbothareavailablerightawayornot.Inotherwords,itnormalizesthenowandlater,suchthatwecanrelyonapredictableoutcomeoftheadd(..)operation.

Byusinganadd(..)thatistemporallyconsistent--itbehavesthesameacrossnowandlatertimes--theasynccodeismucheasiertoreasonabout.

Toputitmoreplainly:toconsistentlyhandlebothnowandlater,wemakebothofthemlater:alloperationsbecomeasync.

Ofcourse,thisroughcallbacks-basedapproachleavesmuchtobedesired.It'sjustafirsttinysteptowardrealizingthebenefitsofreasoningaboutfuturevalueswithoutworryingaboutthetimeaspectofwhenit'savailableornot.

We'lldefinitelygointoalotmoredetailaboutPromiseslaterinthechapter--sodon'tworryifsomeofthisisconfusing--butlet'sjustbrieflyglimpseathowwecanexpressthex+yexampleviaPromises:

functionadd(xPromise,yPromise){

//`Promise.all([..])`takesanarrayofpromises,

//andreturnsanewpromisethatwaitsonthem

//alltofinish

returnPromise.all([xPromise,yPromise])

//whenthatpromiseisresolved,let'stakethe

//received`X`and`Y`valuesandaddthemtogether.

.then(function(values){

//`values`isanarrayofthemessagesfromthe

//previouslyresolvedpromises

returnvalues[0]+values[1];

});

}

//`fetchX()`and`fetchY()`returnpromisesfor

//theirrespectivevalues,whichmaybeready

//*now*or*later*.

add(fetchX(),fetchY())

//wegetapromisebackforthesumofthose

//twonumbers.

//nowwechain-call`then(..)`towaitforthe

//resolutionofthatreturnedpromise.

.then(function(sum){

console.log(sum);//thatwaseasier!

});

PromiseValue

Page 41: You Don't Know JS: Async & Performance

TherearetwolayersofPromisesinthissnippet.

fetchX()andfetchY()arecalleddirectly,andthevaluestheyreturn(promises!)arepassedintoadd(..).Theunderlyingvaluesthosepromisesrepresentmaybereadynoworlater,buteachpromisenormalizesthebehaviortobethesameregardless.WereasonaboutXandYvaluesinatime-independentway.Theyarefuturevalues.

Thesecondlayeristhepromisethatadd(..)creates(viaPromise.all([..]))andreturns,whichwewaitonbycallingthen(..).Whentheadd(..)operationcompletes,oursumfuturevalueisreadyandwecanprintitout.Wehideinsideofadd(..)thelogicforwaitingontheXandYfuturevalues.

Note:Insideadd(..),thePromise.all([..])callcreatesapromise(whichiswaitingonpromiseXandpromiseYtoresolve).Thechainedcallto.then(..)createsanotherpromise,whichthereturnvalues[0]+values[1]lineimmediatelyresolves(withtheresultoftheaddition).Thus,thethen(..)callwechainofftheendoftheadd(..)call--attheendofthesnippet--isactuallyoperatingonthatsecondpromisereturned,ratherthanthefirstonecreatedbyPromise.all([..]).Also,thoughwearenotchainingofftheendofthatsecondthen(..),ittoohascreatedanotherpromise,hadwechosentoobserve/useit.ThisPromisechainingstuffwillbeexplainedinmuchgreaterdetaillaterinthischapter.

Justlikewithcheeseburgerorders,it'spossiblethattheresolutionofaPromiseisrejectioninsteadoffulfillment.UnlikeafulfilledPromise,wherethevalueisalwaysprogrammatic,arejectionvalue--commonlycalleda"rejectionreason"--caneitherbesetdirectlybytheprogramlogic,oritcanresultimplicitlyfromaruntimeexception.

WithPromises,thethen(..)callcanactuallytaketwofunctions,thefirstforfulfillment(asshownearlier),andthesecondforrejection:

add(fetchX(),fetchY())

.then(

//fullfillmenthandler

function(sum){

console.log(sum);

},

//rejectionhandler

function(err){

console.error(err);//bummer!

}

);

IfsomethingwentwronggettingXorY,orsomethingsomehowfailedduringtheaddition,thepromisethatadd(..)returnsisrejected,andthesecondcallbackerrorhandlerpassedtothen(..)willreceivetherejectionvaluefromthepromise.

BecausePromisesencapsulatethetime-dependentstate--waitingonthefulfillmentorrejectionoftheunderlyingvalue--fromtheoutside,thePromiseitselfistime-independent,andthusPromisescanbecomposed(combined)inpredictablewaysregardlessofthetimingoroutcomeunderneath.

Moreover,onceaPromiseisresolved,itstaysthatwayforever--itbecomesanimmutablevalueatthatpoint--andcanthenbeobservedasmanytimesasnecessary.

Note:BecauseaPromiseisexternallyimmutableonceresolved,it'snowsafetopassthatvaluearoundtoanypartyandknowthatitcannotbemodifiedaccidentallyormaliciously.ThisisespeciallytrueinrelationtomultiplepartiesobservingtheresolutionofaPromise.Itisnotpossibleforonepartytoaffectanotherparty'sabilitytoobservePromiseresolution.Immutabilitymaysoundlikeanacademictopic,butit'sactuallyoneofthemostfundamentalandimportantaspectsofPromisedesign,andshouldn'tbecasuallypassedover.

That'soneofthemostpowerfulandimportantconceptstounderstandaboutPromises.Withafairamountofwork,youcouldadhoccreatethesameeffectswithnothingbutuglycallbackcomposition,butthat'snotreallyaneffectivestrategy,especiallybecauseyouhavetodoitoverandoveragain.

Promisesareaneasilyrepeatablemechanismforencapsulatingandcomposingfuturevalues.

Page 42: You Don't Know JS: Async & Performance

Aswejustsaw,anindividualPromisebehavesasafuturevalue.Butthere'sanotherwaytothinkoftheresolutionofaPromise:asaflow-controlmechanism--atemporalthis-then-that--fortwoormorestepsinanasynchronoustask.

Let'simaginecallingafunctionfoo(..)toperformsometask.Wedon'tknowaboutanyofitsdetails,nordowecare.Itmaycompletethetaskrightaway,oritmaytakeawhile.

Wejustsimplyneedtoknowwhenfoo(..)finishessothatwecanmoveontoournexttask.Inotherwords,we'dlikeawaytobenotifiedoffoo(..)'scompletionsothatwecancontinue.

IntypicalJavaScriptfashion,ifyouneedtolistenforanotification,you'dlikelythinkofthatintermsofevents.Sowecouldreframeourneedfornotificationasaneedtolistenforacompletion(orcontinuation)eventemittedbyfoo(..).

Note:Whetheryoucallita"completionevent"ora"continuationevent"dependsonyourperspective.Isthefocusmoreonwhathappenswithfoo(..),orwhathappensafterfoo(..)finishes?Bothperspectivesareaccurateanduseful.Theeventnotificationtellsusthatfoo(..)hascompleted,butalsothatit'sOKtocontinuewiththenextstep.Indeed,thecallbackyoupasstobecalledfortheeventnotificationisitselfwhatwe'vepreviouslycalledacontinuation.Becausecompletioneventisabitmorefocusedonthefoo(..),whichmorehasourattentionatpresent,weslightlyfavorcompletioneventfortherestofthistext.

Withcallbacks,the"notification"wouldbeourcallbackinvokedbythetask(foo(..)).ButwithPromises,weturntherelationshiparound,andexpectthatwecanlistenforaneventfromfoo(..),andwhennotified,proceedaccordingly.

First,considersomepseudocode:

foo(x){

//startdoingsomethingthatcouldtakeawhile

}

foo(42)

on(foo"completion"){

//nowwecandothenextstep!

}

on(foo"error"){

//oops,somethingwentwrongin`foo(..)`

}

Wecallfoo(..)andthenwesetuptwoeventlisteners,onefor"completion"andonefor"error"--thetwopossiblefinaloutcomesofthefoo(..)call.Inessence,foo(..)doesn'tevenappeartobeawarethatthecallingcodehassubscribedtotheseevents,whichmakesforaveryniceseparationofconcerns.

Unfortunately,suchcodewouldrequiresome"magic"oftheJSenvironmentthatdoesn'texist(andwouldlikelybeabitimpractical).Here'sthemorenaturalwaywecouldexpressthatinJS:

functionfoo(x){

//startdoingsomethingthatcouldtakeawhile

//makea`listener`eventnotification

//capabilitytoreturn

returnlistener;

}

varevt=foo(42);

evt.on("completion",function(){

//nowwecandothenextstep!

});

evt.on("failure",function(err){

//oops,somethingwentwrongin`foo(..)`

});

CompletionEvent

Page 43: You Don't Know JS: Async & Performance

foo(..)expresslycreatesaneventsubscriptioncapabilitytoreturnback,andthecallingcodereceivesandregistersthetwoeventhandlersagainstit.

Theinversionfromnormalcallback-orientedcodeshouldbeobvious,andit'sintentional.Insteadofpassingthecallbackstofoo(..),itreturnsaneventcapabilitywecallevt,whichreceivesthecallbacks.

ButifyourecallfromChapter2,callbacksthemselvesrepresentaninversionofcontrol.Soinvertingthecallbackpatternisactuallyaninversionofinversion,oranuninversionofcontrol--restoringcontrolbacktothecallingcodewherewewantedittobeinthefirstplace.

Oneimportantbenefitisthatmultipleseparatepartsofthecodecanbegiventheeventlisteningcapability,andtheycanallindependentlybenotifiedofwhenfoo(..)completestoperformsubsequentstepsafteritscompletion:

varevt=foo(42);

//let`bar(..)`listento`foo(..)`'scompletion

bar(evt);

//also,let`baz(..)`listento`foo(..)`'scompletion

baz(evt);

Uninversionofcontrolenablesanicerseparationofconcerns,wherebar(..)andbaz(..)don'tneedtobeinvolvedinhowfoo(..)iscalled.Similarly,foo(..)doesn'tneedtoknoworcarethatbar(..)andbaz(..)existorarewaitingtobenotifiedwhenfoo(..)completes.

Essentially,thisevtobjectisaneutralthird-partynegotiationbetweentheseparateconcerns.

Asyoumayhaveguessedbynow,theevteventlisteningcapabilityisananalogyforaPromise.

InaPromise-basedapproach,theprevioussnippetwouldhavefoo(..)creatingandreturningaPromiseinstance,andthatpromisewouldthenbepassedtobar(..)andbaz(..).

Note:ThePromiseresolution"events"welistenforaren'tstrictlyevents(thoughtheycertainlybehavelikeeventsforthesepurposes),andthey'renottypicallycalled"completion"or"error".Instead,weusethen(..)toregistera"then"event.Orperhapsmoreprecisely,then(..)registers"fulfillment"and/or"rejection"event(s),thoughwedon'tseethosetermsusedexplicitlyinthecode.

Consider:

functionfoo(x){

//startdoingsomethingthatcouldtakeawhile

//constructandreturnapromise

returnnewPromise(function(resolve,reject){

//eventually,call`resolve(..)`or`reject(..)`,

//whicharetheresolutioncallbacksfor

//thepromise.

});

}

varp=foo(42);

bar(p);

baz(p);

Note:ThepatternshownwithnewPromise(function(..){..})isgenerallycalledthe"revealingconstructor".Thefunctionpassedinisexecutedimmediately(notasyncdeferred,ascallbackstothen(..)are),andit'sprovidedtwo

Promise"Events"

Page 44: You Don't Know JS: Async & Performance

parameters,whichinthiscasewe'venamedresolveandreject.Thesearetheresolutionfunctionsforthepromise.resolve(..)generallysignalsfulfillment,andreject(..)signalsrejection.

Youcanprobablyguesswhattheinternalsofbar(..)andbaz(..)mightlooklike:

functionbar(fooPromise){

//listenfor`foo(..)`tocomplete

fooPromise.then(

function(){

//`foo(..)`hasnowfinished,so

//do`bar(..)`'stask

},

function(){

//oops,somethingwentwrongin`foo(..)`

}

);

}

//dittofor`baz(..)`

Promiseresolutiondoesn'tnecessarilyneedtoinvolvesendingalongamessage,asitdidwhenwewereexaminingPromisesasfuturevalues.Itcanjustbeaflow-controlsignal,asusedintheprevioussnippet.

Anotherwaytoapproachthisis:

functionbar(){

//`foo(..)`hasdefinitelyfinished,so

//do`bar(..)`'stask

}

functionoopsBar(){

//oops,somethingwentwrongin`foo(..)`,

//so`bar(..)`didn'trun

}

//dittofor`baz()`and`oopsBaz()`

varp=foo(42);

p.then(bar,oopsBar);

p.then(baz,oopsBaz);

Note:Ifyou'veseenPromise-basedcodingbefore,youmightbetemptedtobelievethatthelasttwolinesofthatcodecouldbewrittenasp.then(..).then(..),usingchaining,ratherthanp.then(..);p.then(..).Thatwouldhaveanentirelydifferentbehavior,sobecareful!Thedifferencemightnotbeclearrightnow,butit'sactuallyadifferentasyncpatternthanwe'veseenthusfar:splitting/forking.Don'tworry!We'llcomebacktothispointlaterinthischapter.

Insteadofpassingtheppromisetobar(..)andbaz(..),weusethepromisetocontrolwhenbar(..)andbaz(..)willgetexecuted,ifever.Theprimarydifferenceisintheerrorhandling.

Inthefirstsnippet'sapproach,bar(..)iscalledregardlessofwhetherfoo(..)succeedsorfails,andithandlesitsownfallbacklogicifit'snotifiedthatfoo(..)failed.Thesameistrueforbaz(..),obviously.

Inthesecondsnippet,bar(..)onlygetscallediffoo(..)succeeds,andotherwiseoopsBar(..)getscalled.Dittoforbaz(..).

Neitherapproachiscorrectperse.Therewillbecaseswhereoneispreferredovertheother.

Ineithercase,thepromisepthatcomesbackfromfoo(..)isusedtocontrolwhathappensnext.

Moreover,thefactthatbothsnippetsendupcallingthen(..)twiceagainstthesamepromisepillustratesthepointmadeearlier,whichisthatPromises(onceresolved)retaintheirsameresolution(fulfillmentorrejection)forever,andcansubsequentlybeobservedasmanytimesasnecessary.

Page 45: You Don't Know JS: Async & Performance

Wheneverpisresolved,thenextstepwillalwaysbethesame,bothnowandlater.

InPromises-land,animportantdetailishowtoknowforsureifsomevalueisagenuinePromiseornot.Ormoredirectly,isitavaluethatwillbehavelikeaPromise?

GiventhatPromisesareconstructedbythenewPromise(..)syntax,youmightthinkthatpinstanceofPromisewouldbeanacceptablecheck.Butunfortunately,thereareanumberofreasonsthat'snottotallysufficient.

Mainly,youcanreceiveaPromisevaluefromanotherbrowserwindow(iframe,etc.),whichwouldhaveitsownPromisedifferentfromtheoneinthecurrentwindow/frame,andthatcheckwouldfailtoidentifythePromiseinstance.

Moreover,alibraryorframeworkmaychoosetovenditsownPromisesandnotusethenativeES6Promiseimplementationtodoso.Infact,youmayverywellbeusingPromiseswithlibrariesinolderbrowsersthathavenoPromiseatall.

WhenwediscussPromiseresolutionprocesseslaterinthischapter,itwillbecomemoreobviouswhyanon-genuine-but-Promise-likevaluewouldstillbeveryimportanttobeabletorecognizeandassimilate.Butfornow,justtakemywordforitthatit'sacriticalpieceofthepuzzle.

Assuch,itwasdecidedthatthewaytorecognizeaPromise(orsomethingthatbehaveslikeaPromise)wouldbetodefinesomethingcalleda"thenable"asanyobjectorfunctionwhichhasathen(..)methodonit.ItisassumedthatanysuchvalueisaPromise-conformingthenable.

Thegeneraltermfor"typechecks"thatmakeassumptionsaboutavalue's"type"basedonitsshape(whatpropertiesarepresent)iscalled"ducktyping"--"Ifitlookslikeaduck,andquackslikeaduck,itmustbeaduck"(seetheTypes&Grammartitleofthisbookseries).Sotheducktypingcheckforathenablewouldroughlybe:

if(

p!==null&&

(

typeofp==="object"||

typeofp==="function"

)&&

typeofp.then==="function"

){

//assumeit'sathenable!

}

else{

//notathenable

}

Yuck!Settingasidethefactthatthislogicisabituglytoimplementinvariousplaces,there'ssomethingdeeperandmoretroublinggoingon.

IfyoutrytofulfillaPromisewithanyobject/functionvaluethathappenstohaveathen(..)functiononit,butyouweren'tintendingittobetreatedasaPromise/thenable,you'reoutofluck,becauseitwillautomaticallyberecognizedasthenableandtreatedwithspecialrules(seelaterinthechapter).

Thisiseventrueifyoudidn'trealizethevaluehasathen(..)onit.Forexample:

varo={then:function(){}};

//make`v`be`[[Prototype]]`-linkedto`o`

varv=Object.create(o);

v.someStuff="cool";

v.otherStuff="notsocool";

v.hasOwnProperty("then");//false

ThenableDuckTyping

Page 46: You Don't Know JS: Async & Performance

vdoesn'tlooklikeaPromiseorthenableatall.It'sjustaplainobjectwithsomepropertiesonit.You'reprobablyjustintendingtosendthatvaluearoundlikeanyotherobject.

Butunknowntoyou,visalso[[Prototype]]-linked(seethethis&ObjectPrototypestitleofthisbookseries)toanotherobjecto,whichhappenstohaveathen(..)onit.Sothethenableducktypingcheckswillthinkandassumevisathenable.Uhoh.

Itdoesn'tevenneedtobesomethingasdirectlyintentionalasthat:

Object.prototype.then=function(){};

Array.prototype.then=function(){};

varv1={hello:"world"};

varv2=["Hello","World"];

Bothv1andv2willbeassumedtobethenables.Youcan'tcontrolorpredictifanyothercodeaccidentallyormaliciouslyaddsthen(..)toObject.prototype,Array.prototype,oranyoftheothernativeprototypes.Andifwhat'sspecifiedisafunctionthatdoesn'tcalleitherofitsparametersascallbacks,thenanyPromiseresolvedwithsuchavaluewilljustsilentlyhangforever!Crazy.

Soundimplausibleorunlikely?Perhaps.

Butkeepinmindthattherewereseveralwell-knownnon-PromiselibrariespreexistinginthecommunitypriortoES6thathappenedtoalreadyhaveamethodonthemcalledthen(..).Someofthoselibrarieschosetorenametheirownmethodstoavoidcollision(thatsucks!).Othershavesimplybeenrelegatedtotheunfortunatestatusof"incompatiblewithPromise-basedcoding"inrewardfortheirinabilitytochangetogetoutoftheway.

Thestandardsdecisiontohijackthepreviouslynonreserved--andcompletelygeneral-purposesounding--thenpropertynamemeansthatnovalue(oranyofitsdelegates),eitherpast,present,orfuture,canhaveathen(..)functionpresent,eitheronpurposeorbyaccident,orthatvaluewillbeconfusedforathenableinPromisessystems,whichwillprobablycreatebugsthatarereallyhardtotrackdown.

Warning:IdonotlikehowweendedupwithducktypingofthenablesforPromiserecognition.Therewereotheroptions,suchas"branding"oreven"anti-branding";whatwegotseemslikeaworst-casecompromise.Butit'snotalldoomandgloom.Thenableducktypingcanbehelpful,aswe'llseelater.JustbewarethatthenableducktypingcanbehazardousifitincorrectlyidentifiessomethingasaPromisethatisn't.

We'venowseentwostronganalogiesthatexplaindifferentaspectsofwhatPromisescandoforourasynccode.Butifwestopthere,we'vemissedperhapsthesinglemostimportantcharacteristicthatthePromisepatternestablishes:trust.

Whereasthefuturevaluesandcompletioneventsanalogiesplayoutexplicitlyinthecodepatternswe'veexplored,itwon'tbeentirelyobviouswhyorhowPromisesaredesignedtosolvealloftheinversionofcontroltrustissueswelaidoutinthe"TrustIssues"sectionofChapter2.Butwithalittledigging,wecanuncoversomeimportantguaranteesthatrestoretheconfidenceinasynccodingthatChapter2toredown!

Let'sstartbyreviewingthetrustissueswithcallbacks-onlycoding.Whenyoupassacallbacktoautilityfoo(..),itmight:

CallthecallbacktooearlyCallthecallbacktoolate(ornever)CallthecallbacktoofewortoomanytimesFailtopassalonganynecessaryenvironment/parametersswallowanyerrors/exceptionsthatmayhappen

ThecharacteristicsofPromisesareintentionallydesignedtoprovideuseful,repeatableanswerstoalltheseconcerns.

PromiseTrust

Page 47: You Don't Know JS: Async & Performance

Primarily,thisisaconcernofwhethercodecanintroduceZalgo-likeeffects(seeChapter2),wheresometimesataskfinishessynchronouslyandsometimesasynchronously,whichcanleadtoraceconditions.

Promisesbydefinitioncannotbesusceptibletothisconcern,becauseevenanimmediatelyfulfilledPromise(likenewPromise(function(resolve){resolve(42);}))cannotbeobservedsynchronously.

Thatis,whenyoucallthen(..)onaPromise,evenifthatPromisewasalreadyresolved,thecallbackyouprovidetothen(..)willalwaysbecalledasynchronously(formoreonthis,referbackto"Jobs"inChapter1).

NomoreneedtoinsertyourownsetTimeout(..,0)hacks.PromisespreventZalgoautomatically.

Similartothepreviouspoint,aPromise'sthen(..)registeredobservationcallbacksareautomaticallyscheduledwheneitherresolve(..)orreject(..)arecalledbythePromisecreationcapability.Thosescheduledcallbackswillpredictablybefiredatthenextasynchronousmoment(see"Jobs"inChapter1).

It'snotpossibleforsynchronousobservation,soit'snotpossibleforasynchronouschainoftaskstoruninsuchawaytoineffect"delay"anothercallbackfromhappeningasexpected.Thatis,whenaPromiseisresolved,allthen(..)registeredcallbacksonitwillbecalled,inorder,immediatelyatthenextasynchronousopportunity(again,see"Jobs"inChapter1),andnothingthathappensinsideofoneofthosecallbackscanaffect/delaythecallingoftheothercallbacks.

Forexample:

p.then(function(){

p.then(function(){

console.log("C");

});

console.log("A");

});

p.then(function(){

console.log("B");

});

//ABC

Here,"C"cannotinterruptandprecede"B",byvirtueofhowPromisesaredefinedtooperate.

It'simportanttonote,though,thattherearelotsofnuancesofschedulingwheretherelativeorderingbetweencallbackschainedofftwoseparatePromisesisnotreliablypredictable.

Iftwopromisesp1andp2arebothalreadyresolved,itshouldbetruethatp1.then(..);p2.then(..)wouldendupcallingthecallback(s)forp1beforetheonesforp2.Buttherearesubtlecaseswherethatmightnotbetrue,suchasthefollowing:

varp3=newPromise(function(resolve,reject){

resolve("B");

});

varp1=newPromise(function(resolve,reject){

resolve(p3);

});

varp2=newPromise(function(resolve,reject){

resolve("A");

});

p1.then(function(v){

console.log(v);

});

CallingTooEarly

CallingTooLate

PromiseSchedulingQuirks

Page 48: You Don't Know JS: Async & Performance

p2.then(function(v){

console.log(v);

});

//AB<--notBAasyoumightexpect

We'llcoverthismorelater,butasyoucansee,p1isresolvednotwithanimmediatevalue,butwithanotherpromisep3whichisitselfresolvedwiththevalue"B".Thespecifiedbehavioristounwrapp3intop1,butasynchronously,sop1'scallback(s)arebehindp2'scallback(s)intheasynchronusJobqueue(seeChapter1).

Toavoidsuchnuancednightmares,youshouldneverrelyonanythingabouttheordering/schedulingofcallbacksacrossPromises.Infact,agoodpracticeisnottocodeinsuchawaywheretheorderingofmultiplecallbacksmattersatall.Avoidthatifyoucan.

Thisisaverycommonconcern.It'saddressableinseveralwayswithPromises.

First,nothing(notevenaJSerror)canpreventaPromisefromnotifyingyouofitsresolution(ifit'sresolved).IfyouregisterbothfulfillmentandrejectioncallbacksforaPromise,andthePromisegetsresolved,oneofthetwocallbackswillalwaysbecalled.

Ofcourse,ifyourcallbacksthemselveshaveJSerrors,youmaynotseetheoutcomeyouexpect,butthecallbackwillinfacthavebeencalled.We'llcoverlaterhowtobenotifiedofanerrorinyourcallback,becauseeventhosedon'tgetswallowed.

ButwhatifthePromiseitselfnevergetsresolvedeitherway?EventhatisaconditionthatPromisesprovideananswerfor,usingahigherlevelabstractioncalleda"race":

//autilityfortimingoutaPromise

functiontimeoutPromise(delay){

returnnewPromise(function(resolve,reject){

setTimeout(function(){

reject("Timeout!");

},delay);

});

}

//setupatimeoutfor`foo()`

Promise.race([

foo(),//attempt`foo()`

timeoutPromise(3000)//giveit3seconds

])

.then(

function(){

//`foo(..)`fulfilledintime!

},

function(err){

//either`foo()`rejected,oritjust

//didn'tfinishintime,soinspect

//`err`toknowwhich

}

);

TherearemoredetailstoconsiderwiththisPromisetimeoutpattern,butwe'llcomebacktoitlater.

Importantly,wecanensureasignalastotheoutcomeoffoo(),topreventitfromhangingourprogramindefinitely.

Bydefinition,oneistheappropriatenumberoftimesforthecallbacktobecalled.The"toofew"casewouldbezerocalls,whichisthesameasthe"never"casewejustexamined.

NeverCallingtheCallback

CallingTooFeworTooManyTimes

Page 49: You Don't Know JS: Async & Performance

The"toomany"caseiseasytoexplain.Promisesaredefinedsothattheycanonlyberesolvedonce.IfforsomereasonthePromisecreationcodetriestocallresolve(..)orreject(..)multipletimes,ortriestocallboth,thePromisewillacceptonlythefirstresolution,andwillsilentlyignoreanysubsequentattempts.

BecauseaPromisecanonlyberesolvedonce,anythen(..)registeredcallbackswillonlyeverbecalledonce(each).

Ofcourse,ifyouregisterthesamecallbackmorethanonce,(e.g.,p.then(f);p.then(f);),it'llbecalledasmanytimesasitwasregistered.Theguaranteethataresponsefunctioniscalledonlyoncedoesnotpreventyoufromshootingyourselfinthefoot.

Promisescanhave,atmost,oneresolutionvalue(fulfillmentorrejection).

Ifyoudon'texplicitlyresolvewithavalueeitherway,thevalueisundefined,asistypicalinJS.Butwhateverthevalue,itwillalwaysbepassedtoallregistered(andappropriate:fulfillmentorrejection)callbacks,eithernoworinthefuture.

Somethingtobeawareof:Ifyoucallresolve(..)orreject(..)withmultipleparameters,allsubsequentparametersbeyondthefirstwillbesilentlyignored.Althoughthatmightseemaviolationoftheguaranteewejustdescribed,it'snotexactly,becauseitconstitutesaninvalidusageofthePromisemechanism.OtherinvalidusagesoftheAPI(suchascallingresolve(..)multipletimes)aresimilarlyprotected,sothePromisebehaviorhereisconsistent(ifnotatinybitfrustrating).

Ifyouwanttopassalongmultiplevalues,youmustwraptheminanothersinglevaluethatyoupass,suchasanarrayoranobject.

Asforenvironment,functionsinJSalwaysretaintheirclosureofthescopeinwhichthey'redefined(seetheScope&Closurestitleofthisseries),sotheyofcoursewouldcontinuetohaveaccesstowhateversurroundingstateyouprovide.Ofcourse,thesameistrueofcallbacks-onlydesign,sothisisn'taspecificaugmentationofbenefitfromPromises--butit'saguaranteewecanrelyonnonetheless.

Inthebasesense,thisisarestatementofthepreviouspoint.IfyourejectaPromisewithareason(akaerrormessage),thatvalueispassedtotherejectioncallback(s).

Butthere'ssomethingmuchbiggeratplayhere.IfatanypointinthecreationofaPromise,orintheobservationofitsresolution,aJSexceptionerroroccurs,suchasaTypeErrororReferenceError,thatexceptionwillbecaught,anditwillforcethePromiseinquestiontobecomerejected.

Forexample:

varp=newPromise(function(resolve,reject){

foo.bar();//`foo`isnotdefined,soerror!

resolve(42);//nevergetshere:(

});

p.then(

functionfulfilled(){

//nevergetshere:(

},

functionrejected(err){

//`err`willbea`TypeError`exceptionobject

//fromthe`foo.bar()`line.

}

);

TheJSexceptionthatoccursfromfoo.bar()becomesaPromiserejectionthatyoucancatchandrespondto.

Thisisanimportantdetail,becauseiteffectivelysolvesanotherpotentialZalgomoment,whichisthaterrorscouldcreateasynchronousreactionwhereasnonerrorswouldbeasynchronous.PromisesturnevenJSexceptionsintoasynchronous

FailingtoPassAlongAnyParameters/Environment

SwallowingAnyErrors/Exceptions

Page 50: You Don't Know JS: Async & Performance

behavior,therebyreducingtheraceconditionchancesgreatly.

ButwhathappensifaPromiseisfulfilled,butthere'saJSexceptionerrorduringtheobservation(inathen(..)registeredcallback)?Eventhosearen'tlost,butyoumayfindhowthey'rehandledabitsurprising,untilyoudiginalittledeeper:

varp=newPromise(function(resolve,reject){

resolve(42);

});

p.then(

functionfulfilled(msg){

foo.bar();

console.log(msg);//nevergetshere:(

},

functionrejected(err){

//nevergetshereeither:(

}

);

Wait,thatmakesitseemliketheexceptionfromfoo.bar()reallydidgetswallowed.Neverfear,itdidn't.Butsomethingdeeperiswrong,whichisthatwe'vefailedtolistenforit.Thep.then(..)callitselfreturnsanotherpromise,andit'sthatpromisethatwillberejectedwiththeTypeErrorexception.

Whycouldn'titjustcalltheerrorhandlerwehavedefinedthere?Seemslikealogicalbehavioronthesurface.ButitwouldviolatethefundamentalprinciplethatPromisesareimmutableonceresolved.pwasalreadyfulfilledtothevalue42,soitcan'tlaterbechangedtoarejectionjustbecausethere'sanerrorinobservingp'sresolution.

Besidestheprincipleviolation,suchbehaviorcouldwreakhavoc,ifsaythereweremultiplethen(..)registeredcallbacksonthepromisep,becausesomewouldgetcalledandotherswouldn't,anditwouldbeveryopaqueastowhy.

There'sonelastdetailtoexaminetoestablishtrustbasedonthePromisepattern.

You'venodoubtnoticedthatPromisesdon'tgetridofcallbacksatall.Theyjustchangewherethecallbackispassedto.Insteadofpassingacallbacktofoo(..),wegetsomething(ostensiblyagenuinePromise)backfromfoo(..),andwepassthecallbacktothatsomethinginstead.

Butwhywouldthisbeanymoretrustablethanjustcallbacksalone?HowcanwebesurethesomethingwegetbackisinfactatrustablePromise?Isn'titbasicallyalljustahouseofcardswherewecantrustonlybecausewealreadytrusted?

Oneofthemostimportant,butoftenoverlooked,detailsofPromisesisthattheyhaveasolutiontothisissueaswell.IncludedwiththenativeES6PromiseimplementationisPromise.resolve(..).

Ifyoupassanimmediate,non-Promise,non-thenablevaluetoPromise.resolve(..),yougetapromisethat'sfulfilledwiththatvalue.Inotherwords,thesetwopromisesp1andp2willbehavebasicallyidentically:

varp1=newPromise(function(resolve,reject){

resolve(42);

});

varp2=Promise.resolve(42);

ButifyoupassagenuinePromisetoPromise.resolve(..),youjustgetthesamepromiseback:

varp1=Promise.resolve(42);

varp2=Promise.resolve(p1);

p1===p2;//true

TrustablePromise?

Page 51: You Don't Know JS: Async & Performance

Evenmoreimportantly,ifyoupassanon-PromisethenablevaluetoPromise.resolve(..),itwillattempttounwrapthatvalue,andtheunwrappingwillkeepgoinguntilaconcretefinalnon-Promise-likevalueisextracted.

Recallourpreviousdiscussionofthenables?

Consider:

varp={

then:function(cb){

cb(42);

}

};

//thisworksOK,butonlybygoodfortune

p

.then(

functionfulfilled(val){

console.log(val);//42

},

functionrejected(err){

//nevergetshere

}

);

Thispisathenable,butit'snotagenuinePromise.Luckily,it'sreasonable,asmostwillbe.Butwhatifyougotbackinsteadsomethingthatlookedlike:

varp={

then:function(cb,errcb){

cb(42);

errcb("evillaugh");

}

};

p

.then(

functionfulfilled(val){

console.log(val);//42

},

functionrejected(err){

//oops,shouldn'thaverun

console.log(err);//evillaugh

}

);

Thispisathenablebutit'snotsowellbehavedofapromise.Isitmalicious?OrisitjustignorantofhowPromisesshouldwork?Itdoesn'treallymatter,tobehonest.Ineithercase,it'snottrustableasis.

Nonetheless,wecanpasseitheroftheseversionsofptoPromise.resolve(..),andwe'llgetthenormalized,saferesultwe'dexpect:

Promise.resolve(p)

.then(

functionfulfilled(val){

console.log(val);//42

},

functionrejected(err){

//nevergetshere

}

);

Promise.resolve(..)willacceptanythenable,andwillunwrapittoitsnon-thenablevalue.ButyougetbackfromPromise.resolve(..)areal,genuinePromiseinitsplace,onethatyoucantrust.IfwhatyoupassedinisalreadyagenuinePromise,youjustgetitrightback,sothere'snodownsideatalltofilteringthroughPromise.resolve(..)togaintrust.

Page 52: You Don't Know JS: Async & Performance

Solet'ssaywe'recallingafoo(..)utilityandwe'renotsurewecantrustitsreturnvaluetobeawell-behavingPromise,butweknowit'satleastathenable.Promise.resolve(..)willgiveusatrustablePromisewrappertochainoffof:

//don'tjustdothis:

foo(42)

.then(function(v){

console.log(v);

});

//instead,dothis:

Promise.resolve(foo(42))

.then(function(v){

console.log(v);

});

Note:AnotherbeneficialsideeffectofwrappingPromise.resolve(..)aroundanyfunction'sreturnvalue(thenableornot)isthatit'saneasywaytonormalizethatfunctioncallintoawell-behavingasynctask.Iffoo(42)returnsanimmediatevaluesometimes,oraPromiseothertimes,Promise.resolve(foo(42))makessureit'salwaysaPromiseresult.AndavoidingZalgomakesformuchbettercode.

Hopefullythepreviousdiscussionnowfully"resolves"(punintended)inyourmindwhythePromiseistrustable,andmoreimportantly,whythattrustissocriticalinbuildingrobust,maintainablesoftware.

CanyouwriteasynccodeinJSwithouttrust?Ofcourseyoucan.WeJSdevelopershavebeencodingasyncwithnothingbutcallbacksfornearlytwodecades.

Butonceyoustartquestioningjusthowmuchyoucantrustthemechanismsyoubuildupontoactuallybepredictableandreliable,youstarttorealizecallbackshaveaprettyshakytrustfoundation.

Promisesareapatternthataugmentscallbackswithtrustablesemantics,sothatthebehaviorismorereason-ableandmorereliable.Byuninvertingtheinversionofcontrolofcallbacks,weplacethecontrolwithatrustablesystem(Promises)thatwasdesignedspecificallytobringsanitytoourasync.

We'vehintedatthisacoupleoftimesalready,butPromisesarenotjustamechanismforasingle-stepthis-then-thatsortofoperation.That'sthebuildingblock,ofcourse,butitturnsoutwecanstringmultiplePromisestogethertorepresentasequenceofasyncsteps.

ThekeytomakingthisworkisbuiltontwobehaviorsintrinsictoPromises:

Everytimeyoucallthen(..)onaPromise,itcreatesandreturnsanewPromise,whichwecanchainwith.Whatevervalueyoureturnfromthethen(..)call'sfulfillmentcallback(thefirstparameter)isautomaticallysetasthefulfillmentofthechainedPromise(fromthefirstpoint).

Let'sfirstillustratewhatthatmeans,andthenwe'llderivehowthathelpsuscreateasyncsequencesofflowcontrol.Considerthefollowing:

varp=Promise.resolve(21);

varp2=p.then(function(v){

console.log(v);//21

//fulfill`p2`withvalue`42`

returnv*2;

});

//chainoff`p2`

p2.then(function(v){

TrustBuilt

ChainFlow

Page 53: You Don't Know JS: Async & Performance

console.log(v);//42

});

Byreturningv*2(i.e.,42),wefulfillthep2promisethatthefirstthen(..)callcreatedandreturned.Whenp2'sthen(..)callruns,it'sreceivingthefulfillmentfromthereturnv*2statement.Ofcourse,p2.then(..)createsyetanotherpromise,whichwecouldhavestoredinap3variable.

Butit'salittleannoyingtohavetocreateanintermediatevariablep2(orp3,etc.).Thankfully,wecaneasilyjustchainthesetogether:

varp=Promise.resolve(21);

p

.then(function(v){

console.log(v);//21

//fulfillthechainedpromisewithvalue`42`

returnv*2;

})

//here'sthechainedpromise

.then(function(v){

console.log(v);//42

});

Sonowthefirstthen(..)isthefirststepinanasyncsequence,andthesecondthen(..)isthesecondstep.Thiscouldkeepgoingforaslongasyouneededittoextend.Justkeepchainingoffapreviousthen(..)witheachautomaticallycreatedPromise.

Butthere'ssomethingmissinghere.Whatifwewantstep2towaitforstep1todosomethingasynchronous?We'reusinganimmediatereturnstatement,whichimmediatelyfulfillsthechainedpromise.

ThekeytomakingaPromisesequencetrulyasynccapableateverystepistorecallhowPromise.resolve(..)operateswhenwhatyoupasstoitisaPromiseorthenableinsteadofafinalvalue.Promise.resolve(..)directlyreturnsareceivedgenuinePromise,oritunwrapsthevalueofareceivedthenable--andkeepsgoingrecursivelywhileitkeepsunwrappingthenables.

ThesamesortofunwrappinghappensifyoureturnathenableorPromisefromthefulfillment(orrejection)handler.Consider:

varp=Promise.resolve(21);

p.then(function(v){

console.log(v);//21

//createapromiseandreturnit

returnnewPromise(function(resolve,reject){

//fulfillwithvalue`42`

resolve(v*2);

});

})

.then(function(v){

console.log(v);//42

});

Eventhoughwewrapped42upinapromisethatwereturned,itstillgotunwrappedandendedupastheresolutionofthechainedpromise,suchthatthesecondthen(..)stillreceived42.Ifweintroduceasynchronytothatwrappingpromise,everythingstillnicelyworksthesame:

varp=Promise.resolve(21);

p.then(function(v){

console.log(v);//21

Page 54: You Don't Know JS: Async & Performance

//createapromisetoreturn

returnnewPromise(function(resolve,reject){

//introduceasynchrony!

setTimeout(function(){

//fulfillwithvalue`42`

resolve(v*2);

},100);

});

})

.then(function(v){

//runsafterthe100msdelayinthepreviousstep

console.log(v);//42

});

That'sincrediblypowerful!Nowwecanconstructasequenceofhowevermanyasyncstepswewant,andeachstepcandelaythenextstep(ornot!),asnecessary.

Ofcourse,thevaluepassingfromsteptostepintheseexamplesisoptional.Ifyoudon'treturnanexplicitvalue,animplicitundefinedisassumed,andthepromisesstillchaintogetherthesameway.EachPromiseresolutionisthusjustasignaltoproceedtothenextstep.

Tofurtherthechainillustration,let'sgeneralizeadelay-Promisecreation(withoutresolutionmessages)intoautilitywecanreuseformultiplesteps:

functiondelay(time){

returnnewPromise(function(resolve,reject){

setTimeout(resolve,time);

});

}

delay(100)//step1

.then(functionSTEP2(){

console.log("step2(after100ms)");

returndelay(200);

})

.then(functionSTEP3(){

console.log("step3(afteranother200ms)");

})

.then(functionSTEP4(){

console.log("step4(nextJob)");

returndelay(50);

})

.then(functionSTEP5(){

console.log("step5(afteranother50ms)");

})

...

Callingdelay(200)createsapromisethatwillfulfillin200ms,andthenwereturnthatfromthefirstthen(..)fulfillmentcallback,whichcausesthesecondthen(..)'spromisetowaitonthat200mspromise.

Note:Asdescribed,technicallytherearetwopromisesinthatinterchange:the200ms-delaypromiseandthechainedpromisethatthesecondthen(..)chainsfrom.Butyoumayfinditeasiertomentallycombinethesetwopromisestogether,becausethePromisemechanismautomaticallymergestheirstatesforyou.Inthatrespect,youcouldthinkofreturndelay(200)ascreatingapromisethatreplacestheearlier-returnedchainedpromise.

Tobehonest,though,sequencesofdelayswithnomessagepassingisn'taterriblyusefulexampleofPromiseflowcontrol.Let'slookatascenariothat'salittlemorepractical.

Insteadoftimers,let'sconsidermakingAjaxrequests:

//assumean`ajax({url},{callback})`utility

//Promise-awareajax

functionrequest(url){

returnnewPromise(function(resolve,reject){

//the`ajax(..)`callbackshouldbeour

//promise's`resolve(..)`function

ajax(url,resolve);

Page 55: You Don't Know JS: Async & Performance

});

}

Wefirstdefinearequest(..)utilitythatconstructsapromisetorepresentthecompletionoftheajax(..)call:

request("http://some.url.1/")

.then(function(response1){

returnrequest("http://some.url.2/?v="+response1);

})

.then(function(response2){

console.log(response2);

});

Note:DeveloperscommonlyencountersituationsinwhichtheywanttodoPromise-awareasyncflowcontrolwithutilitiesthatarenotthemselvesPromise-enabled(likeajax(..)here,whichexpectsacallback).AlthoughthenativeES6Promisemechanismdoesn'tautomaticallysolvethispatternforus,practicallyallPromiselibrariesdo.Theyusuallycallthisprocess"lifting"or"promisifying"orsomevariationthereof.We'llcomebacktothistechniquelater.

UsingthePromise-returningrequest(..),wecreatethefirststepinourchainimplicitlybycallingitwiththefirstURL,andchainoffthatreturnedpromisewiththefirstthen(..).

Onceresponse1comesback,weusethatvaluetoconstructasecondURL,andmakeasecondrequest(..)call.Thatsecondrequest(..)promiseisreturnedsothatthethirdstepinourasyncflowcontrolwaitsforthatAjaxcalltocomplete.Finally,weprintresponse2onceitreturns.

ThePromisechainweconstructisnotonlyaflowcontrolthatexpressesamultistepasyncsequence,butitalsoactsasamessagechanneltopropagatemessagesfromsteptostep.

WhatifsomethingwentwronginoneofthestepsofthePromisechain?Anerror/exceptionisonaper-Promisebasis,whichmeansit'spossibletocatchsuchanerroratanypointinthechain,andthatcatchingactstosortof"reset"thechainbacktonormaloperationatthatpoint:

//step1:

request("http://some.url.1/")

//step2:

.then(function(response1){

foo.bar();//undefined,error!

//nevergetshere

returnrequest("http://some.url.2/?v="+response1);

})

//step3:

.then(

functionfulfilled(response2){

//nevergetshere

},

//rejectionhandlertocatchtheerror

functionrejected(err){

console.log(err);//`TypeError`from`foo.bar()`error

return42;

}

)

//step4:

.then(function(msg){

console.log(msg);//42

});

Whentheerroroccursinstep2,therejectionhandlerinstep3catchesit.Thereturnvalue(42inthissnippet),ifany,fromthatrejectionhandlerfulfillsthepromiseforthenextstep(4),suchthatthechainisnowbackinafulfillmentstate.

Note:Aswediscussedearlier,whenreturningapromisefromafulfillmenthandler,it'sunwrappedandcandelaythenextstep.That'salsotrueforreturningpromisesfromrejectionhandlers,suchthatifthereturn42instep3insteadreturneda

Page 56: You Don't Know JS: Async & Performance

promise,thatpromisecoulddelaystep4.Athrownexceptioninsideeitherthefulfillmentorrejectionhandlerofathen(..)callcausesthenext(chained)promisetobeimmediatelyrejectedwiththatexception.

Ifyoucallthen(..)onapromise,andyouonlypassafulfillmenthandlertoit,anassumedrejectionhandlerissubstituted:

varp=newPromise(function(resolve,reject){

reject("Oops");

});

varp2=p.then(

functionfulfilled(){

//nevergetshere

}

//assumedrejectionhandler,ifomittedor

//anyothernon-functionvaluepassed

//function(err){

//throwerr;

//}

);

Asyoucansee,theassumedrejectionhandlersimplyrethrowstheerror,whichendsupforcingp2(thechainedpromise)torejectwiththesameerrorreason.Inessence,thisallowstheerrortocontinuepropagatingalongaPromisechainuntilanexplicitlydefinedrejectionhandlerisencountered.

Note:We'llcovermoredetailsoferrorhandlingwithPromisesalittlelater,becausethereareothernuanceddetailstobeconcernedabout.

Ifapropervalidfunctionisnotpassedasthefulfillmenthandlerparametertothen(..),there'salsoadefaulthandlersubstituted:

varp=Promise.resolve(42);

p.then(

//assumedfulfillmenthandler,ifomittedor

//anyothernon-functionvaluepassed

//function(v){

//returnv;

//}

null,

functionrejected(err){

//nevergetshere

}

);

Asyoucansee,thedefaultfulfillmenthandlersimplypasseswhatevervalueitreceivesalongtothenextstep(Promise).

Note:Thethen(null,function(err){..})pattern--onlyhandlingrejections(ifany)butlettingfulfillmentspassthrough--hasashortcutintheAPI:catch(function(err){..}).We'llcovercatch(..)morefullyinthenextsection.

Let'sreviewbrieflytheintrinsicbehaviorsofPromisesthatenablechainingflowcontrol:

Athen(..)callagainstonePromiseautomaticallyproducesanewPromisetoreturnfromthecall.Insidethefulfillment/rejectionhandlers,ifyoureturnavalueoranexceptionisthrown,thenewreturned(chainable)Promiseisresolvedaccordingly.IfthefulfillmentorrejectionhandlerreturnsaPromise,itisunwrapped,sothatwhateveritsresolutioniswillbecometheresolutionofthechainedPromisereturnedfromthecurrentthen(..).

Whilechainingflowcontrolishelpful,it'sprobablymostaccuratetothinkofitasasidebenefitofhowPromisescompose(combine)together,ratherthanthemainintent.Aswe'vediscussedindetailseveraltimesalready,Promisesnormalizeasynchronyandencapsulatetime-dependentvaluestate,andthatiswhatletsuschainthemtogetherinthisusefulway.

Certainly,thesequentialexpressivenessofthechain(this-then-this-then-this...)isabigimprovementoverthetangledmessofcallbacksasweidentifiedinChapter2.Butthere'sstillafairamountofboilerplate(then(..)andfunction(){..})to

Page 57: You Don't Know JS: Async & Performance

wadethrough.Inthenextchapter,we'llseeasignificantlynicerpatternforsequentialflowcontrolexpressivity,withgenerators.

There'ssomeslightconfusionaroundtheterms"resolve,""fulfill,"and"reject"thatweneedtoclearup,beforeyougettoomuchdeeperintolearningaboutPromises.Let'sfirstconsiderthePromise(..)constructor:

varp=newPromise(function(X,Y){

//X()forfulfillment

//Y()forrejection

});

Asyoucansee,twocallbacks(herelabeledXandY)areprovided.ThefirstisusuallyusedtomarkthePromiseasfulfilled,andthesecondalwaysmarksthePromiseasrejected.Butwhat'sthe"usually"about,andwhatdoesthatimplyaboutaccuratelynamingthoseparameters?

Ultimately,it'sjustyourusercodeandtheidentifiernamesaren'tinterpretedbytheenginetomeananything,soitdoesn'ttechnicallymatter;foo(..)andbar(..)areequallyfunctional.Butthewordsyouusecanaffectnotonlyhowyouarethinkingaboutthecode,buthowotherdevelopersonyourteamwillthinkaboutit.Thinkingwronglyaboutcarefullyorchestratedasynccodeisalmostsurelygoingtobeworsethanthespaghetti-callbackalternatives.

Soitactuallydoeskindofmatterwhatyoucallthem.

Thesecondparameteriseasytodecide.Almostallliteratureusesreject(..)asitsname,andbecausethat'sexactly(andonly!)whatitdoes,that'saverygoodchoiceforthename.I'dstronglyrecommendyoualwaysusereject(..).

Butthere'salittlemoreambiguityaroundthefirstparameter,whichinPromiseliteratureisoftenlabeledresolve(..).Thatwordisobviouslyrelatedto"resolution,"whichiswhat'susedacrosstheliterature(includingthisbook)todescribesettingafinalvalue/statetoaPromise.We'vealreadyused"resolvethePromise"severaltimestomeaneitherfulfillingorrejectingthePromise.

ButifthisparameterseemstobeusedtospecificallyfulfillthePromise,whyshouldn'twecallitfulfill(..)insteadofresolve(..)tobemoreaccurate?Toanswerthatquestion,let'salsotakealookattwoofthePromiseAPImethods:

varfulfilledPr=Promise.resolve(42);

varrejectedPr=Promise.reject("Oops");

Promise.resolve(..)createsaPromisethat'sresolvedtothevaluegiventoit.Inthisexample,42isanormal,non-Promise,non-thenablevalue,sothefulfilledpromisefulfilledPriscreatedforthevalue42.Promise.reject("Oops")createstherejectedpromiserejectedPrforthereason"Oops".

Let'snowillustratewhytheword"resolve"(suchasinPromise.resolve(..))isunambiguousandindeedmoreaccurate,ifusedexplicitlyinacontextthatcouldresultineitherfulfillmentorrejection:

varrejectedTh={

then:function(resolved,rejected){

rejected("Oops");

}

};

varrejectedPr=Promise.resolve(rejectedTh);

Aswediscussedearlierinthischapter,Promise.resolve(..)willreturnareceivedgenuinePromisedirectly,orunwrapareceivedthenable.Ifthatthenableunwrappingrevealsarejectedstate,thePromisereturnedfromPromise.resolve(..)isinfactinthatsamerejectedstate.

Terminology:Resolve,Fulfill,andReject

Page 58: You Don't Know JS: Async & Performance

SoPromise.resolve(..)isagood,accuratenamefortheAPImethod,becauseitcanactuallyresultineitherfulfillmentorrejection.

ThefirstcallbackparameterofthePromise(..)constructorwillunwrapeitherathenable(identicallytoPromise.resolve(..))oragenuinePromise:

varrejectedPr=newPromise(function(resolve,reject){

//resolvethispromisewitharejectedpromise

resolve(Promise.reject("Oops"));

});

rejectedPr.then(

functionfulfilled(){

//nevergetshere

},

functionrejected(err){

console.log(err);//"Oops"

}

);

Itshouldbeclearnowthatresolve(..)istheappropriatenameforthefirstcallbackparameterofthePromise(..)constructor.

Warning:Thepreviouslymentionedreject(..)doesnotdotheunwrappingthatresolve(..)does.IfyoupassaPromise/thenablevaluetoreject(..),thatuntouchedvaluewillbesetastherejectionreason.AsubsequentrejectionhandlerwouldreceivetheactualPromise/thenableyoupassedtoreject(..),notitsunderlyingimmediatevalue.

Butnowlet'sturnourattentiontothecallbacksprovidedtothen(..).Whatshouldtheybecalled(bothinliteratureandincode)?Iwouldsuggestfulfilled(..)andrejected(..):

functionfulfilled(msg){

console.log(msg);

}

functionrejected(err){

console.error(err);

}

p.then(

fulfilled,

rejected

);

Inthecaseofthefirstparametertothen(..),it'sunambiguouslyalwaysthefulfillmentcase,sothere'snoneedforthedualityof"resolve"terminology.Asasidenote,theES6specificationusesonFulfilled(..)andonRejected(..)tolabelthesetwocallbacks,sotheyareaccurateterms.

We'vealreadyseenseveralexamplesofhowPromiserejection--eitherintentionalthroughcallingreject(..)oraccidentalthroughJSexceptions--allowssanererrorhandlinginasynchronousprogramming.Let'scirclebackthoughandbeexplicitaboutsomeofthedetailsthatweglossedover.

Themostnaturalformoferrorhandlingformostdevelopersisthesynchronoustry..catchconstruct.Unfortunately,it'ssynchronous-only,soitfailstohelpinasynccodepatterns:

functionfoo(){

setTimeout(function(){

baz.bar();

},100);

}

ErrorHandling

Page 59: You Don't Know JS: Async & Performance

try{

foo();

//laterthrowsglobalerrorfrom`baz.bar()`

}

catch(err){

//nevergetshere

}

try..catchwouldcertainlybenicetohave,butitdoesn'tworkacrossasyncoperations.Thatis,unlessthere'ssomeadditionalenvironmentalsupport,whichwe'llcomebacktowithgeneratorsinChapter4.

Incallbacks,somestandardshaveemergedforpatternederrorhandling,mostnotablythe"error-firstcallback"style:

functionfoo(cb){

setTimeout(function(){

try{

varx=baz.bar();

cb(null,x);//success!

}

catch(err){

cb(err);

}

},100);

}

foo(function(err,val){

if(err){

console.error(err);//bummer:(

}

else{

console.log(val);

}

});

Note:Thetry..catchhereworksonlyfromtheperspectivethatthebaz.bar()callwilleithersucceedorfailimmediately,synchronously.Ifbaz.bar()wasitselfitsownasynccompletingfunction,anyasyncerrorsinsideitwouldnotbecatchable.

Thecallbackwepasstofoo(..)expectstoreceiveasignalofanerrorbythereservedfirstparametererr.Ifpresent,errorisassumed.Ifnot,successisassumed.

Thissortoferrorhandlingistechnicallyasynccapable,butitdoesn'tcomposewellatall.Multiplelevelsoferror-firstcallbackswoventogetherwiththeseubiquitousifstatementchecksinevitablywillleadyoutotheperilsofcallbackhell(seeChapter2).

SowecomebacktoerrorhandlinginPromises,withtherejectionhandlerpassedtothen(..).Promisesdon'tusethepopular"error-firstcallback"designstyle,butinsteaduse"splitcallbacks"style;there'sonecallbackforfulfillmentandoneforrejection:

varp=Promise.reject("Oops");

p.then(

functionfulfilled(){

//nevergetshere

},

functionrejected(err){

console.log(err);//"Oops"

}

);

Whilethispatternoferrorhandlingmakesfinesenseonthesurface,thenuancesofPromiseerrorhandlingareoftenafairbitmoredifficulttofullygrasp.

Consider:

varp=Promise.resolve(42);

Page 60: You Don't Know JS: Async & Performance

p.then(

functionfulfilled(msg){

//numbersdon'thavestringfunctions,

//sowillthrowanerror

console.log(msg.toLowerCase());

},

functionrejected(err){

//nevergetshere

}

);

Ifthemsg.toLowerCase()legitimatelythrowsanerror(itdoes!),whydoesn'tourerrorhandlergetnotified?Asweexplainedearlier,it'sbecausethaterrorhandlerisfortheppromise,whichhasalreadybeenfulfilledwithvalue42.Theppromiseisimmutable,sotheonlypromisethatcanbenotifiedoftheerroristheonereturnedfromp.then(..),whichinthiscasewedon'tcapture.

ThatshouldpaintaclearpictureofwhyerrorhandlingwithPromisesiserror-prone(punintended).It'sfartooeasytohaveerrorsswallowed,asthisisveryrarelywhatyou'dintend.

Warning:IfyouusethePromiseAPIinaninvalidwayandanerroroccursthatpreventsproperPromiseconstruction,theresultwillbeanimmediatelythrownexception,notarejectedPromise.SomeexamplesofincorrectusagethatfailPromiseconstruction:newPromise(null),Promise.all(),Promise.race(42),andsoon.Youcan'tgetarejectedPromiseifyoudon'tusethePromiseAPIvalidlyenoughtoactuallyconstructaPromiseinthefirstplace!

JeffAtwoodnotedyearsago:programminglanguagesareoftensetupinsuchawaythatbydefault,developersfallintothe"pitofdespair"(http://blog.codinghorror.com/falling-into-the-pit-of-success/)--whereaccidentsarepunished--andthatyouhavetotryhardertogetitright.Heimploredustoinsteadcreatea"pitofsuccess,"wherebydefaultyoufallintoexpected(successful)action,andthuswouldhavetotryhardtofail.

Promiseerrorhandlingisunquestionably"pitofdespair"design.Bydefault,itassumesthatyouwantanyerrortobeswallowedbythePromisestate,andifyouforgettoobservethatstate,theerrorsilentlylanguishes/diesinobscurity--usuallydespair.

Toavoidlosinganerrortothesilenceofaforgotten/discardedPromise,somedevelopershaveclaimedthata"bestpractice"forPromisechainsistoalwaysendyourchainwithafinalcatch(..),like:

varp=Promise.resolve(42);

p.then(

functionfulfilled(msg){

//numbersdon'thavestringfunctions,

//sowillthrowanerror

console.log(msg.toLowerCase());

}

)

.catch(handleErrors);

Becausewedidn'tpassarejectionhandlertothethen(..),thedefaulthandlerwassubstituted,whichsimplypropagatestheerrortothenextpromiseinthechain.Assuch,botherrorsthatcomeintop,anderrorsthatcomeafterpinitsresolution(likethemsg.toLowerCase()one)willfilterdowntothefinalhandleErrors(..).

Problemsolved,right?Notsofast!

WhathappensifhandleErrors(..)itselfalsohasanerrorinit?Whocatchesthat?There'sstillyetanotherunattendedpromise:theonecatch(..)returns,whichwedon'tcaptureanddon'tregisterarejectionhandlerfor.

Youcan'tjuststickanothercatch(..)ontheendofthatchain,becauseittoocouldfail.ThelaststepinanyPromisechain,whateveritis,alwayshasthepossibility,evendecreasinglyso,ofdanglingwithanuncaughterrorstuckinsideanunobservedPromise.

PitofDespair

Page 61: You Don't Know JS: Async & Performance

Soundlikeanimpossibleconundrumyet?

It'snotexactlyaneasyproblemtosolvecompletely.Thereareotherwaystoapproachitwhichmanywouldsayarebetter.

SomePromiselibrarieshaveaddedmethodsforregisteringsomethinglikea"globalunhandledrejection"handler,whichwouldbecalledinsteadofagloballythrownerror.Buttheirsolutionforhowtoidentifyanerroras"uncaught"istohaveanarbitrary-lengthtimer,say3seconds,runningfromtimeofrejection.IfaPromiseisrejectedbutnoerrorhandlerisregisteredbeforethetimerfires,thenit'sassumedthatyouwon'teverberegisteringahandler,soit's"uncaught."

Inpractice,thishasworkedwellformanylibraries,asmostusagepatternsdon'ttypicallycallforsignificantdelaybetweenPromiserejectionandobservationofthatrejection.Butthispatternistroublesomebecause3secondsissoarbitrary(evenifempirical),andalsobecausethereareindeedsomecaseswhereyouwantaPromisetoholdontoitsrejectednessforsomeindefiniteperiodoftime,andyoudon'treallywanttohaveyour"uncaught"handlercalledforallthosefalsepositives(not-yet-handled"uncaughterrors").

AnothermorecommonsuggestionisthatPromisesshouldhaveadone(..)addedtothem,whichessentiallymarksthePromisechainas"done."done(..)doesn'tcreateandreturnaPromise,sothecallbackspassedtodone(..)areobviouslynotwireduptoreportproblemstoachainedPromisethatdoesn'texist.

Sowhathappensinstead?It'streatedasyoumightusuallyexpectinuncaughterrorconditions:anyexceptioninsideadone(..)rejectionhandlerwouldbethrownasaglobaluncaughterror(inthedeveloperconsole,basically):

varp=Promise.resolve(42);

p.then(

functionfulfilled(msg){

//numbersdon'thavestringfunctions,

//sowillthrowanerror

console.log(msg.toLowerCase());

}

)

.done(null,handleErrors);

//if`handleErrors(..)`causeditsownexception,itwould

//bethrowngloballyhere

Thismightsoundmoreattractivethanthenever-endingchainorthearbitrarytimeouts.Butthebiggestproblemisthatit'snotpartoftheES6standard,sonomatterhowgooditsounds,atbestit'salotlongerwayofffrombeingareliableandubiquitoussolution.

Arewejuststuck,then?Notentirely.

Browsershaveauniquecapabilitythatourcodedoesnothave:theycantrackandknowforsurewhenanyobjectgetsthrownawayandgarbagecollected.So,browserscantrackPromiseobjects,andwhenevertheygetgarbagecollected,iftheyhavearejectioninthem,thebrowserknowsforsurethiswasalegitimate"uncaughterror,"andcanthusconfidentlyknowitshouldreportittothedeveloperconsole.

Note:Atthetimeofthiswriting,bothChromeandFirefoxhaveearlyattemptsatthatsortof"uncaughtrejection"capability,thoughsupportisincompleteatbest.

However,ifaPromisedoesn'tgetgarbagecollected--it'sexceedinglyeasyforthattoaccidentallyhappenthroughlotsofdifferentcodingpatterns--thebrowser'sgarbagecollectionsniffingwon'thelpyouknowanddiagnosethatyouhaveasilentlyrejectedPromiselayingaround.

Isthereanyotheralternative?Yes.

UncaughtHandling

PitofSuccess

Page 62: You Don't Know JS: Async & Performance

Thefollowingisjusttheoretical,howPromisescouldbesomedaychangedtobehave.Ibelieveitwouldbefarsuperiortowhatwecurrentlyhave.AndIthinkthischangewouldbepossibleevenpost-ES6becauseIdon'tthinkitwouldbreakwebcompatibilitywithES6Promises.Moreover,itcanbepolyfilled/prollyfilledin,ifyou'recareful.Let'stakealook:

Promisescoulddefaulttoreporting(tothedeveloperconsole)anyrejection,onthenextJoboreventlooptick,ifatthatexactmomentnoerrorhandlerhasbeenregisteredforthePromise.ForthecaseswhereyouwantarejectedPromisetoholdontoitsrejectedstateforanindefiniteamountoftimebeforeobserving,youcouldcalldefer(),whichsuppressesautomaticerrorreportingonthatPromise.

IfaPromiseisrejected,itdefaultstonoisilyreportingthatfacttothedeveloperconsole(insteadofdefaultingtosilence).Youcanoptoutofthatreportingeitherimplicitly(byregisteringanerrorhandlerbeforerejection),orexplicitly(withdefer()).Ineithercase,youcontrolthefalsepositives.

Consider:

varp=Promise.reject("Oops").defer();

//`foo(..)`isPromise-aware

foo(42)

.then(

functionfulfilled(){

returnp;

},

functionrejected(err){

//handle`foo(..)`error

}

);

...

Whenwecreatep,weknowwe'regoingtowaitawhiletouse/observeitsrejection,sowecalldefer()--thusnoglobalreporting.defer()simplyreturnsthesamepromise,forchainingpurposes.

Thepromisereturnedfromfoo(..)getsanerrorhandlerattachedrightaway,soit'simplicitlyoptedoutandnoglobalreportingforitoccurseither.

Butthepromisereturnedfromthethen(..)callhasnodefer()orerrorhandlerattached,soifitrejects(frominsideeitherresolutionhandler),thenitwillbereportedtothedeveloperconsoleasanuncaughterror.

Thisdesignisapitofsuccess.Bydefault,allerrorsareeitherhandledorreported--whatalmostalldevelopersinalmostallcaseswouldexpect.Youeitherhavetoregisterahandleroryouhavetointentionallyoptout,andindicateyouintendtodefererrorhandlinguntillater;you'reoptingfortheextraresponsibilityinjustthatspecificcase.

Theonlyrealdangerinthisapproachisifyoudefer()aPromisebutthenfailtoactuallyeverobserve/handleitsrejection.

Butyouhadtointentionallycalldefer()tooptintothatpitofdespair--thedefaultwasthepitofsuccess--sothere'snotmuchelsewecoulddotosaveyoufromyourownmistakes.

Ithinkthere'sstillhopeforPromiseerrorhandling(post-ES6).Ihopethepowersthatbewillrethinkthesituationandconsiderthisalternative.Inthemeantime,youcanimplementthisyourself(achallengingexerciseforthereader!),oruseasmarterPromiselibrarythatdoessoforyou!

Note:Thisexactmodelforerrorhandling/reportingisimplementedinmyasynquencePromiseabstractionlibrary,whichwillbediscussedinAppendixAofthisbook.

We'vealreadyimplicitlyseenthesequencepatternwithPromisechains(this-then-this-then-thatflowcontrol)buttherearelotsofvariationsonasynchronouspatternsthatwecanbuildasabstractionsontopofPromises.Thesepatternsservetosimplifytheexpressionofasyncflowcontrol--whichhelpsmakeourcodemorereason-ableandmoremaintainable--

PromisePatterns

Page 63: You Don't Know JS: Async & Performance

eveninthemostcomplexpartsofourprograms.

TwosuchpatternsarecodifieddirectlyintothenativeES6Promiseimplementation,sowegetthemforfree,touseasbuildingblocksforotherpatterns.

Inanasyncsequence(Promisechain),onlyoneasynctaskisbeingcoordinatedatanygivenmoment--step2strictlyfollowsstep1,andstep3strictlyfollowsstep2.Butwhataboutdoingtwoormorestepsconcurrently(aka"inparallel")?

Inclassicprogrammingterminology,a"gate"isamechanismthatwaitsontwoormoreparallel/concurrenttaskstocompletebeforecontinuing.Itdoesn'tmatterwhatordertheyfinishin,justthatallofthemhavetocompleteforthegatetoopenandlettheflowcontrolthrough.

InthePromiseAPI,wecallthispatternall([..]).

SayyouwantedtomaketwoAjaxrequestsatthesametime,andwaitforbothtofinish,regardlessoftheirorder,beforemakingathirdAjaxrequest.Consider:

//`request(..)`isaPromise-awareAjaxutility,

//likewedefinedearlierinthechapter

varp1=request("http://some.url.1/");

varp2=request("http://some.url.2/");

Promise.all([p1,p2])

.then(function(msgs){

//both`p1`and`p2`fulfillandpassin

//theirmessageshere

returnrequest(

"http://some.url.3/?v="+msgs.join(",")

);

})

.then(function(msg){

console.log(msg);

});

Promise.all([..])expectsasingleargument,anarray,consistinggenerallyofPromiseinstances.ThepromisereturnedfromthePromise.all([..])callwillreceiveafulfillmentmessage(msgsinthissnippet)thatisanarrayofallthefulfillmentmessagesfromthepassedinpromises,inthesameorderasspecified(regardlessoffulfillmentorder).

Note:Technically,thearrayofvaluespassedintoPromise.all([..])canincludePromises,thenables,orevenimmediatevalues.EachvalueinthelistisessentiallypassedthroughPromise.resolve(..)tomakesureit'sagenuinePromisetobewaitedon,soanimmediatevaluewilljustbenormalizedintoaPromiseforthatvalue.Ifthearrayisempty,themainPromiseisimmediatelyfulfilled.

ThemainpromisereturnedfromPromise.all([..])willonlybefulfilledifandwhenallitsconstituentpromisesarefulfilled.Ifanyoneofthosepromisesinsteadisrejected,themainPromise.all([..])promiseisimmediatelyrejected,discardingallresultsfromanyotherpromises.

Remembertoalwaysattacharejection/errorhandlertoeverypromise,evenandespeciallytheonethatcomesbackfromPromise.all([..]).

WhilePromise.all([..])coordinatesmultiplePromisesconcurrentlyandassumesallareneededforfulfillment,sometimesyouonlywanttorespondtothe"firstPromisetocrossthefinishline,"lettingtheotherPromisesfallaway.

Thispatternisclassicallycalleda"latch,"butinPromisesit'scalleda"race."

Warning:Whilethemetaphorof"onlythefirstacrossthefinishlinewins"fitsthebehaviorwell,unfortunately"race"iskind

Promise.all([..])

Promise.race([..])

Page 64: You Don't Know JS: Async & Performance

ofaloadedterm,because"raceconditions"aregenerallytakenasbugsinprograms(seeChapter1).Don'tconfusePromise.race([..])with"racecondition."

Promise.race([..])alsoexpectsasinglearrayargument,containingoneormorePromises,thenables,orimmediatevalues.Itdoesn'tmakemuchpracticalsensetohavearacewithimmediatevalues,becausethefirstonelistedwillobviouslywin--likeafootracewhereonerunnerstartsatthefinishline!

SimilartoPromise.all([..]),Promise.race([..])willfulfillifandwhenanyPromiseresolutionisafulfillment,anditwillrejectifandwhenanyPromiseresolutionisarejection.

Warning:A"race"requiresatleastone"runner,"soifyoupassanemptyarray,insteadofimmediatelyresolving,themainrace([..])Promisewillneverresolve.Thisisafootgun!ES6shouldhavespecifiedthatiteitherfulfills,rejects,orjustthrowssomesortofsynchronouserror.Unfortunately,becauseofprecedenceinPromiselibrariespredatingES6Promise,theyhadtoleavethisgotchainthere,sobecarefulnevertosendinanemptyarray.

Let'srevisitourpreviousconcurrentAjaxexample,butinthecontextofaracebetweenp1andp2:

//`request(..)`isaPromise-awareAjaxutility,

//likewedefinedearlierinthechapter

varp1=request("http://some.url.1/");

varp2=request("http://some.url.2/");

Promise.race([p1,p2])

.then(function(msg){

//either`p1`or`p2`willwintherace

returnrequest(

"http://some.url.3/?v="+msg

);

})

.then(function(msg){

console.log(msg);

});

Becauseonlyonepromisewins,thefulfillmentvalueisasinglemessage,notanarrayasitwasforPromise.all([..]).

Wesawthisexampleearlier,illustratinghowPromise.race([..])canbeusedtoexpressthe"promisetimeout"pattern:

//`foo()`isaPromise-awarefunction

//`timeoutPromise(..)`,definedealier,returns

//aPromisethatrejectsafteraspecifieddelay

//setupatimeoutfor`foo()`

Promise.race([

foo(),//attempt`foo()`

timeoutPromise(3000)//giveit3seconds

])

.then(

function(){

//`foo(..)`fulfilledintime!

},

function(err){

//either`foo()`rejected,oritjust

//didn'tfinishintime,soinspect

//`err`toknowwhich

}

);

Thistimeoutpatternworkswellinmostcases.Buttherearesomenuancestoconsider,andfranklytheyapplytobothPromise.race([..])andPromise.all([..])equally.

TimeoutRace

"Finally"

Page 65: You Don't Know JS: Async & Performance

Thekeyquestiontoaskis,"Whathappenstothepromisesthatgetdiscarded/ignored?"We'renotaskingthatquestionfromtheperformanceperspective--theywouldtypicallyendupgarbagecollectioneligible--butfromthebehavioralperspective(sideeffects,etc.).Promisescannotbecanceled--andshouldn'tbeasthatwoulddestroytheexternalimmutabilitytrustdiscussedinthe"PromiseUncancelable"sectionlaterinthischapter--sotheycanonlybesilentlyignored.

Butwhatiffoo()inthepreviousexampleisreservingsomesortofresourceforusage,butthetimeoutfiresfirstandcausesthatpromisetobeignored?Isthereanythinginthispatternthatproactivelyfreesthereservedresourceafterthetimeout,orotherwisecancelsanysideeffectsitmayhavehad?Whatifallyouwantedwastologthefactthatfoo()timedout?

SomedevelopershaveproposedthatPromisesneedafinally(..)callbackregistration,whichisalwayscalledwhenaPromiseresolves,andallowsyoutospecifyanycleanupthatmaybenecessary.Thisdoesn'texistinthespecificationatthemoment,butitmaycomeinES7+.We'llhavetowaitandsee.

Itmightlooklike:

varp=Promise.resolve(42);

p.then(something)

.finally(cleanup)

.then(another)

.finally(cleanup);

Note:InvariousPromiselibraries,finally(..)stillcreatesandreturnsanewPromise(tokeepthechaingoing).Ifthecleanup(..)functionweretoreturnaPromise,itwouldbelinkedintothechain,whichmeansyoucouldstillhavetheunhandledrejectionissueswediscussedearlier.

Inthemeantime,wecouldmakeastatichelperutilitythatletsusobserve(withoutinterfering)theresolutionofaPromise:

//polyfill-safeguardcheck

if(!Promise.observe){

Promise.observe=function(pr,cb){

//side-observe`pr`'sresolution

pr.then(

functionfulfilled(msg){

//schedulecallbackasync(asJob)

Promise.resolve(msg).then(cb);

},

functionrejected(err){

//schedulecallbackasync(asJob)

Promise.resolve(err).then(cb);

}

);

//returnoriginalpromise

returnpr;

};

}

Here'showwe'duseitinthetimeoutexamplefrombefore:

Promise.race([

Promise.observe(

foo(),//attempt`foo()`

functioncleanup(msg){

//cleanupafter`foo()`,evenifit

//didn'tfinishbeforethetimeout

}

),

timeoutPromise(3000)//giveit3seconds

])

Page 66: You Don't Know JS: Async & Performance

ThisPromise.observe(..)helperisjustanillustrationofhowyoucouldobservethecompletionsofPromiseswithoutinterferingwiththem.OtherPromiselibrarieshavetheirownsolutions.Regardlessofhowyoudoit,you'lllikelyhaveplaceswhereyouwanttomakesureyourPromisesaren'tjustsilentlyignoredbyaccident.

WhilenativeES6Promisescomewithbuilt-inPromise.all([..])andPromise.race([..]),thereareseveralothercommonlyusedpatternswithvariationsonthosesemantics:

none([..])islikeall([..]),butfulfillmentsandrejectionsaretransposed.AllPromisesneedtoberejected--rejectionsbecomethefulfillmentvaluesandviceversa.any([..])islikeall([..]),butitignoresanyrejections,soonlyoneneedstofulfillinsteadofallofthem.first([..])isalikearacewithany([..]),whichisthatitignoresanyrejectionsandfulfillsassoonasthefirstPromisefulfills.last([..])islikefirst([..]),butonlythelatestfulfillmentwins.

SomePromiseabstractionlibrariesprovidethese,butyoucouldalsodefinethemyourselfusingthemechanicsofPromises,race([..])andall([..]).

Forexample,here'showwecoulddefinefirst([..]):

//polyfill-safeguardcheck

if(!Promise.first){

Promise.first=function(prs){

returnnewPromise(function(resolve,reject){

//loopthroughallpromises

prs.forEach(function(pr){

//normalizethevalue

Promise.resolve(pr)

//whicheveronefulfillsfirstwins,and

//getstoresolvethemainpromise

.then(resolve);

});

});

};

}

Note:Thisimplementationoffirst(..)doesnotrejectifallitspromisesreject;itsimplyhangs,muchlikeaPromise.race([])does.Ifdesired,youcouldaddadditionallogictotrackeachpromiserejectionandifallreject,callreject()onthemainpromise.We'llleavethatasanexerciseforthereader.

SometimesyouwanttoiterateoveralistofPromisesandperformsometaskagainstallofthem,muchlikeyoucandowithsynchronousarrays(e.g.,forEach(..),map(..),some(..),andevery(..)).IfthetasktoperformagainsteachPromiseisfundamentallysynchronous,theseworkfine,justasweusedforEach(..)intheprevioussnippet.

Butifthetasksarefundamentallyasynchronous,orcan/shouldotherwisebeperformedconcurrently,youcanuseasyncversionsoftheseutilitiesasprovidedbymanylibraries.

Forexample,let'sconsideranasynchronousmap(..)utilitythattakesanarrayofvalues(couldbePromisesoranythingelse),plusafunction(task)toperformagainsteach.map(..)itselfreturnsapromisewhosefulfillmentvalueisanarraythatholds(inthesamemappingorder)theasyncfulfillmentvaluefromeachtask:

if(!Promise.map){

Promise.map=function(vals,cb){

//newpromisethatwaitsforallmappedpromises

returnPromise.all(

//note:regulararray`map(..)`,turns

//thearrayofvaluesintoanarrayof

//promises

vals.map(function(val){

Variationsonall([..])andrace([..])

ConcurrentIterations

Page 67: You Don't Know JS: Async & Performance

//replace`val`withanewpromisethat

//resolvesafter`val`isasyncmapped

returnnewPromise(function(resolve){

cb(val,resolve);

});

})

);

};

}

Note:Inthisimplementationofmap(..),youcan'tsignalasyncrejection,butifasynchronousexception/erroroccursinsideofthemappingcallback(cb(..)),themainPromise.map(..)returnedpromisewouldreject.

Let'sillustrateusingmap(..)withalistofPromises(insteadofsimplevalues):

varp1=Promise.resolve(21);

varp2=Promise.resolve(42);

varp3=Promise.reject("Oops");

//doublevaluesinlistevenifthey'rein

//Promises

Promise.map([p1,p2,p3],function(pr,done){

//makesuretheitemitselfisaPromise

Promise.resolve(pr)

.then(

//extractvalueas`v`

function(v){

//mapfulfillment`v`tonewvalue

done(v*2);

},

//or,maptopromiserejectionmessage

done

);

})

.then(function(vals){

console.log(vals);//[42,84,"Oops"]

});

Let'sreviewtheES6PromiseAPIthatwe'vealreadyseenunfoldinbitsandpiecesthroughoutthischapter.

Note:ThefollowingAPIisnativeonlyasofES6,buttherearespecification-compliantpolyfills(notjustextendedPromiselibraries)whichcandefinePromiseandallitsassociatedbehaviorsothatyoucanusenativePromiseseveninpre-ES6browsers.Onesuchpolyfillis"NativePromiseOnly"(http://github.com/getify/native-promise-only),whichIwrote!

TherevealingconstructorPromise(..)mustbeusedwithnew,andmustbeprovidedafunctioncallbackthatissynchronously/immediatelycalled.Thisfunctionispassedtwofunctioncallbacksthatactasresolutioncapabilitiesforthepromise.Wecommonlylabeltheseresolve(..)andreject(..):

varp=newPromise(function(resolve,reject){

//`resolve(..)`toresolve/fulfillthepromise

//`reject(..)`torejectthepromise

});

reject(..)simplyrejectsthepromise,butresolve(..)caneitherfulfillthepromiseorrejectit,dependingonwhatit'spassed.Ifresolve(..)ispassedanimmediate,non-Promise,non-thenablevalue,thenthepromiseisfulfilledwiththatvalue.

Butifresolve(..)ispassedagenuinePromiseorthenablevalue,thatvalueisunwrappedrecursively,andwhateveritsfinalresolution/stateiswillbeadoptedbythepromise.

PromiseAPIRecap

newPromise(..)Constructor

Page 68: You Don't Know JS: Async & Performance

Ashortcutforcreatinganalready-rejectedPromiseisPromise.reject(..),sothesetwopromisesareequivalent:

varp1=newPromise(function(resolve,reject){

reject("Oops");

});

varp2=Promise.reject("Oops");

Promise.resolve(..)isusuallyusedtocreateanalready-fulfilledPromiseinasimilarwaytoPromise.reject(..).However,Promise.resolve(..)alsounwrapsthenablevalues(asdiscusssedseveraltimesalready).Inthatcase,thePromisereturnedadoptsthefinalresolutionofthethenableyoupassedin,whichcouldeitherbefulfillmentorrejection:

varfulfilledTh={

then:function(cb){cb(42);}

};

varrejectedTh={

then:function(cb,errCb){

errCb("Oops");

}

};

varp1=Promise.resolve(fulfilledTh);

varp2=Promise.resolve(rejectedTh);

//`p1`willbeafulfilledpromise

//`p2`willbearejectedpromise

Andremember,Promise.resolve(..)doesn'tdoanythingifwhatyoupassisalreadyagenuinePromise;itjustreturnsthevaluedirectly.Sothere'snooverheadtocallingPromise.resolve(..)onvaluesthatyoudon'tknowthenatureof,ifonehappenstoalreadybeagenuinePromise.

EachPromiseinstance(notthePromiseAPInamespace)hasthen(..)andcatch(..)methods,whichallowregisteringoffulfillmentandrejectionhandlersforthePromise.OncethePromiseisresolved,oneortheotherofthesehandlerswillbecalled,butnotboth,anditwillalwaysbecalledasynchronously(see"Jobs"inChapter1).

then(..)takesoneortwoparameters,thefirstforthefulfillmentcallback,andthesecondfortherejectioncallback.Ifeitherisomittedorisotherwisepassedasanon-functionvalue,adefaultcallbackissubstitutedrespectively.Thedefaultfulfillmentcallbacksimplypassesthemessagealong,whilethedefaultrejectioncallbacksimplyrethrows(propagates)theerrorreasonitreceives.

catch(..)takesonlytherejectioncallbackasaparameter,andautomaticallysubstitutesthedefaultfulfillmentcallback,asjustdiscussed.Inotherwords,it'sequivalenttothen(null,..):

p.then(fulfilled);

p.then(fulfilled,rejected);

p.catch(rejected);//or`p.then(null,rejected)`

then(..)andcatch(..)alsocreateandreturnanewpromise,whichcanbeusedtoexpressPromisechainflowcontrol.Ifthefulfillmentorrejectioncallbackshaveanexceptionthrown,thereturnedpromiseisrejected.Ifeithercallbackreturnsanimmediate,non-Promise,non-thenablevalue,thatvalueissetasthefulfillmentforthereturnedpromise.Ifthefulfillmenthandlerspecificallyreturnsapromiseorthenablevalue,thatvalueisunwrappedandbecomestheresolutionofthereturnedpromise.

Promise.resolve(..)andPromise.reject(..)

then(..)andcatch(..)

Page 69: You Don't Know JS: Async & Performance

ThestatichelpersPromise.all([..])andPromise.race([..])ontheES6PromiseAPIbothcreateaPromiseastheirreturnvalue.Theresolutionofthatpromiseiscontrolledentirelybythearrayofpromisesthatyoupassin.

ForPromise.all([..]),allthepromisesyoupassinmustfulfillforthereturnedpromisetofulfill.Ifanypromiseisrejected,themainreturnedpromiseisimmediatelyrejected,too(discardingtheresultsofanyoftheotherpromises).Forfulfillment,youreceiveanarrayofallthepassedinpromises'fulfillmentvalues.Forrejection,youreceivejustthefirstpromiserejectionreasonvalue.Thispatternisclassicallycalleda"gate":allmustarrivebeforethegateopens.

ForPromise.race([..]),onlythefirstpromisetoresolve(fulfillmentorrejection)"wins,"andwhateverthatresolutionisbecomestheresolutionofthereturnedpromise.Thispatternisclassicallycalleda"latch":firstonetoopenthelatchgetsthrough.Consider:

varp1=Promise.resolve(42);

varp2=Promise.resolve("HelloWorld");

varp3=Promise.reject("Oops");

Promise.race([p1,p2,p3])

.then(function(msg){

console.log(msg);//42

});

Promise.all([p1,p2,p3])

.catch(function(err){

console.error(err);//"Oops"

});

Promise.all([p1,p2])

.then(function(msgs){

console.log(msgs);//[42,"HelloWorld"]

});

Warning:Becareful!IfanemptyarrayispassedtoPromise.all([..]),itwillfulfillimmediately,butPromise.race([..])willhangforeverandneverresolve.

TheES6PromiseAPIisprettysimpleandstraightforward.It'satleastgoodenoughtoservethemostbasicofasynccases,andisagoodplacetostartwhenrearrangingyourcodefromcallbackhelltosomethingbetter.

Butthere'sawholelotofasyncsophisticationthatappsoftendemandwhichPromisesthemselveswillbelimitedinaddressing.Inthenextsection,we'lldiveintothoselimitationsasmotivationsforthebenefitofPromiselibraries.

Manyofthedetailswe'lldiscussinthissectionhavealreadybeenalludedtointhischapter,butwe'lljustmakesuretoreviewtheselimitationsspecifically.

WecoveredPromise-flavorederrorhandlingindetailearlierinthischapter.ThelimitationsofhowPromisesaredesigned--howtheychain,specifically--createsaveryeasypitfallwhereanerrorinaPromisechaincanbesilentlyignoredaccidentally.

Butthere'ssomethingelsetoconsiderwithPromiseerrors.BecauseaPromisechainisnothingmorethanitsconstituentPromiseswiredtogether,there'snoentitytorefertotheentirechainasasinglething,whichmeansthere'snoexternalwaytoobserveanyerrorsthatmayoccur.

IfyouconstructaPromisechainthathasnoerrorhandlinginit,anyerroranywhereinthechainwillpropagateindefinitelydownthechain,untilobserved(byregisteringarejectionhandleratsomestep).So,inthatspecificcase,havingareferencetothelastpromiseinthechainisenough(pinthefollowingsnippet),becauseyoucanregisterarejection

Promise.all([..])andPromise.race([..])

PromiseLimitations

SequenceErrorHandling

Page 70: You Don't Know JS: Async & Performance

handlerthere,anditwillbenotifiedofanypropagatederrors:

//`foo(..)`,`STEP2(..)`and`STEP3(..)`are

//allpromise-awareutilities

varp=foo(42)

.then(STEP2)

.then(STEP3);

Althoughitmayseemsneakilyconfusing,pheredoesn'tpointtothefirstpromiseinthechain(theonefromthefoo(42)call),butinsteadfromthelastpromise,theonethatcomesfromthethen(STEP3)call.

Also,nostepinthepromisechainisobservablydoingitsownerrorhandling.Thatmeansthatyoucouldthenregisterarejectionerrorhandleronp,anditwouldbenotifiedifanyerrorsoccuranywhereinthechain:

p.catch(handleErrors);

Butifanystepofthechaininfactdoesitsownerrorhandling(perhapshidden/abstractedawayfromwhatyoucansee),yourhandleErrors(..)won'tbenotified.Thismaybewhatyouwant--itwas,afterall,a"handledrejection"--butitalsomaynotbewhatyouwant.Thecompletelackofabilitytobenotified(of"alreadyhandled"rejectionerrors)isalimitationthatrestrictscapabilitiesinsomeusecases.

It'sbasicallythesamelimitationthatexistswithatry..catchthatcancatchanexceptionandsimplyswallowit.Sothisisn'talimitationuniquetoPromises,butitissomethingwemightwishtohaveaworkaroundfor.

Unfortunately,manytimesthereisnoreferencekeptfortheintermediatestepsinaPromise-chainsequence,sowithoutsuchreferences,youcannotattacherrorhandlerstoreliablyobservetheerrors.

Promisesbydefinitiononlyhaveasinglefulfillmentvalueorasinglerejectionreason.Insimpleexamples,thisisn'tthatbigofadeal,butinmoresophisticatedscenarios,youmayfindthislimiting.

Thetypicaladviceistoconstructavalueswrapper(suchasanobjectorarray)tocontainthesemultiplemessages.Thissolutionworks,butitcanbequiteawkwardandtedioustowrapandunwrapyourmessageswitheverysinglestepofyourPromisechain.

Sometimesyoucantakethisasasignalthatyoucould/shoulddecomposetheproblemintotwoormorePromises.

Imagineyouhaveautilityfoo(..)thatproducestwovalues(xandy)asynchronously:

functiongetY(x){

returnnewPromise(function(resolve,reject){

setTimeout(function(){

resolve((3*x)-1);

},100);

});

}

functionfoo(bar,baz){

varx=bar*baz;

returngetY(x)

.then(function(y){

//wrapbothvaluesintocontainer

return[x,y];

});

}

foo(10,20)

SingleValue

SplittingValues

Page 71: You Don't Know JS: Async & Performance

.then(function(msgs){

varx=msgs[0];

vary=msgs[1];

console.log(x,y);//200599

});

First,let'srearrangewhatfoo(..)returnssothatwedon'thavetowrapxandyintoasinglearrayvaluetotransportthroughonePromise.Instead,wecanwrapeachvalueintoitsownpromise:

functionfoo(bar,baz){

varx=bar*baz;

//returnbothpromises

return[

Promise.resolve(x),

getY(x)

];

}

Promise.all(

foo(10,20)

)

.then(function(msgs){

varx=msgs[0];

vary=msgs[1];

console.log(x,y);

});

Isanarrayofpromisesreallybetterthananarrayofvaluespassedthroughasinglepromise?Syntactically,it'snotmuchofanimprovement.

ButthisapproachmorecloselyembracesthePromisedesigntheory.It'snoweasierinthefuturetorefactortosplitthecalculationofxandyintoseparatefunctions.It'scleanerandmoreflexibletoletthecallingcodedecidehowtoorchestratethetwopromises--usingPromise.all([..])here,butcertainlynottheonlyoption--ratherthantoabstractsuchdetailsawayinsideoffoo(..).

Thevarx=..andvary=..assignmentsarestillawkwardoverhead.Wecanemploysomefunctionaltrickery(hattiptoReginaldBraithwaite,@raganwaldonTwitter)inahelperutility:

functionspread(fn){

returnFunction.apply.bind(fn,null);

}

Promise.all(

foo(10,20)

)

.then(

spread(function(x,y){

console.log(x,y);//200599

})

)

That'sabitnicer!Ofcourse,youcouldinlinethefunctionalmagictoavoidtheextrahelper:

Promise.all(

foo(10,20)

)

.then(Function.apply.bind(

function(x,y){

console.log(x,y);//200599

},

null

));

Unwrap/SpreadArguments

Page 72: You Don't Know JS: Async & Performance

Thesetricksmaybeneat,butES6hasanevenbetteranswerforus:destructuring.Thearraydestructuringassignmentformlookslikethis:

Promise.all(

foo(10,20)

)

.then(function(msgs){

var[x,y]=msgs;

console.log(x,y);//200599

});

Butbestofall,ES6offersthearrayparameterdestructuringform:

Promise.all(

foo(10,20)

)

.then(function([x,y]){

console.log(x,y);//200599

});

We'venowembracedtheone-value-per-Promisemantra,butkeptoursupportingboilerplatetoaminimum!

Note:FormoreinformationonES6destructuringforms,seetheES6&Beyondtitleofthisseries.

OneofthemostintrinsicbehaviorsofPromisesisthataPromisecanonlyberesolvedonce(fulfillmentorrejection).Formanyasyncusecases,you'reonlyretrievingavalueonce,sothisworksfine.

Butthere'salsoalotofasynccasesthatfitintoadifferentmodel--onethat'smoreakintoeventsand/orstreamsofdata.It'snotclearonthesurfacehowwellPromisescanfitintosuchusecases,ifatall.WithoutasignificantabstractionontopofPromises,theywillcompletelyfallshortforhandlingmultiplevalueresolution.

Imagineascenariowhereyoumightwanttofireoffasequenceofasyncstepsinresponsetoastimulus(likeanevent)thatcaninfacthappenmultipletimes,likeabuttonclick.

Thisprobablywon'tworkthewayyouwant:

//`click(..)`bindsthe`"click"`eventtoaDOMelement

//`request(..)`isthepreviouslydefinedPromise-awareAjax

varp=newPromise(function(resolve,reject){

click("#mybtn",resolve);

});

p.then(function(evt){

varbtnID=evt.currentTarget.id;

returnrequest("http://some.url.1/?id="+btnID);

})

.then(function(text){

console.log(text);

});

Thebehaviorhereonlyworksifyourapplicationcallsforthebuttontobeclickedjustonce.Ifthebuttonisclickedasecondtime,theppromisehasalreadybeenresolved,sothesecondresolve(..)callwouldbeignored.

Instead,you'dprobablyneedtoinverttheparadigm,creatingawholenewPromisechainforeacheventfiring:

SingleResolution

Page 73: You Don't Know JS: Async & Performance

click("#mybtn",function(evt){

varbtnID=evt.currentTarget.id;

request("http://some.url.1/?id="+btnID)

.then(function(text){

console.log(text);

});

});

ThisapproachwillworkinthatawholenewPromisesequencewillbefiredoffforeach"click"eventonthebutton.

ButbeyondjusttheuglinessofhavingtodefinetheentirePromisechaininsidetheeventhandler,thisdesigninsomerespectsviolatestheideaofseparationofconcerns/capabilities(SoC).Youmightverywellwanttodefineyoureventhandlerinadifferentplaceinyourcodefromwhereyoudefinetheresponsetotheevent(thePromisechain).That'sprettyawkwardtodointhispattern,withouthelpermechanisms.

Note:Anotherwayofarticulatingthislimitationisthatit'dbeniceifwecouldconstructsomesortof"observable"thatwecansubscribeaPromisechainto.Therearelibrariesthathavecreatedtheseabstractions(suchasRxJS--http://rxjs.codeplex.com/),buttheabstractionscanseemsoheavythatyoucan'tevenseethenatureofPromisesanymore.Suchheavyabstractionbringsimportantquestionstomindsuchaswhether(sansPromises)thesemechanismsareastrustableasPromisesthemselveshavebeendesignedtobe.We'llrevisitthe"Observable"patterninAppendixB.

OneconcretebarriertostartingtousePromisesinyourowncodeisallthecodethatcurrentlyexistswhichisnotalreadyPromise-aware.Ifyouhavelotsofcallback-basedcode,it'sfareasiertojustkeepcodinginthatsamestyle.

"Acodebaseinmotion(withcallbacks)willremaininmotion(withcallbacks)unlessacteduponbyasmart,Promises-awaredeveloper."

Promisesofferadifferentparadigm,andassuch,theapproachtothecodecanbeanywherefromjustalittledifferentto,insomecases,radicallydifferent.Youhavetobeintentionalaboutit,becausePromiseswillnotjustnaturallyshakeoutfromthesameol'waysofdoingcodethathaveservedyouwellthusfar.

Consideracallback-basedscenariolikethefollowing:

functionfoo(x,y,cb){

ajax(

"http://some.url.1/?x="+x+"&y="+y,

cb

);

}

foo(11,31,function(err,text){

if(err){

console.error(err);

}

else{

console.log(text);

}

});

Isitimmediatelyobviouswhatthefirststepsaretoconvertthiscallback-basedcodetoPromise-awarecode?Dependsonyourexperience.Themorepracticeyouhavewithit,themorenaturalitwillfeel.Butcertainly,Promisesdon'tjustadvertiseonthelabelexactlyhowtodoit--there'snoone-size-fits-allanswer--sotheresponsibilityisuptoyou.

Aswe'vecoveredbefore,wedefinitelyneedanAjaxutilitythatisPromise-awareinsteadofcallback-based,whichwecouldcallrequest(..).Youcanmakeyourown,aswehavealready.ButtheoverheadofhavingtomanuallydefinePromise-awarewrappersforeverycallback-basedutilitymakesitlesslikelyyou'llchoosetorefactortoPromise-awarecodingatall.

Promisesoffernodirectanswertothatlimitation.MostPromiselibrariesdoofferahelper,however.Butevenwithouta

Inertia

Page 74: You Don't Know JS: Async & Performance

library,imagineahelperlikethis:

//polyfill-safeguardcheck

if(!Promise.wrap){

Promise.wrap=function(fn){

returnfunction(){

varargs=[].slice.call(arguments);

returnnewPromise(function(resolve,reject){

fn.apply(

null,

args.concat(function(err,v){

if(err){

reject(err);

}

else{

resolve(v);

}

})

);

});

};

};

}

OK,that'smorethanjustatinytrivialutility.However,althoughitmaylookabitintimidating,it'snotasbadasyou'dthink.Ittakesafunctionthatexpectsanerror-firststylecallbackasitslastparameter,andreturnsanewonethatautomaticallycreatesaPromisetoreturn,andsubstitutesthecallbackforyou,wireduptothePromisefulfillment/rejection.

RatherthanwastetoomuchtimetalkingabouthowthisPromise.wrap(..)helperworks,let'sjustlookathowweuseit:

varrequest=Promise.wrap(ajax);

request("http://some.url.1/")

.then(..)

..

Wow,thatwasprettyeasy!

Promise.wrap(..)doesnotproduceaPromise.ItproducesafunctionthatwillproducePromises.Inasense,aPromise-producingfunctioncouldbeseenasa"Promisefactory."Ipropose"promisory"asthenameforsuchathing("Promise"+"factory").

Theactofwrappingacallback-expectingfunctiontobeaPromise-awarefunctionissometimesreferredtoas"lifting"or"promisifying".Buttheredoesn'tseemtobeastandardtermforwhattocalltheresultantfunctionotherthana"liftedfunction",soIlike"promisory"betterasIthinkit'smoredescriptive.

Note:Promisoryisn'tamade-upterm.It'sarealword,anditsdefinitionmeanstocontainorconveyapromise.That'sexactlywhatthesefunctionsaredoing,soitturnsouttobeaprettyperfectterminologymatch!

So,Promise.wrap(ajax)producesanajax(..)promisorywecallrequest(..),andthatpromisoryproducesPromisesforAjaxresponses.

Ifallfunctionswerealreadypromisories,wewouldn'tneedtomakethemourselves,sotheextrastepisatadbitofashame.Butatleastthewrappingpatternis(usually)repeatablesowecanputitintoaPromise.wrap(..)helperasshowntoaidourpromisecoding.

Sobacktoourearlierexample,weneedapromisoryforbothajax(..)andfoo(..):

//makeapromisoryfor`ajax(..)`

varrequest=Promise.wrap(ajax);

//refactor`foo(..)`,butkeepitexternally

//callback-basedforcompatibilitywithother

Page 75: You Don't Know JS: Async & Performance

//partsofthecodefornow--onlyuse

//`request(..)`'spromiseinternally.

functionfoo(x,y,cb){

request(

"http://some.url.1/?x="+x+"&y="+y

)

.then(

functionfulfilled(text){

cb(null,text);

},

cb

);

}

//now,forthiscode'spurposes,makea

//promisoryfor`foo(..)`

varbetterFoo=Promise.wrap(foo);

//andusethepromisory

betterFoo(11,31)

.then(

functionfulfilled(text){

console.log(text);

},

functionrejected(err){

console.error(err);

}

);

Ofcourse,whilewe'rerefactoringfoo(..)touseournewrequest(..)promisory,wecouldjustmakefoo(..)apromisoryitself,insteadofremainingcallback-basedandneedingtomakeandusethesubsequentbetterFoo(..)promisory.Thisdecisionjustdependsonwhetherfoo(..)needstostaycallback-basedcompatiblewithotherpartsofthecodebaseornot.

Consider:

`foo(..)`isnowalsoapromisorybecauseit

delegatestothe`request(..)`promisory

functionfoo(x,y){

returnrequest(

"http://some.url.1/?x="+x+"&y="+y

);

}

foo(11,31)

.then(..)

..

WhileES6Promisesdon'tnativelyshipwithhelpersforsuchpromisorywrapping,mostlibrariesprovidethem,oryoucanmakeyourown.Eitherway,thisparticularlimitationofPromisesisaddressablewithouttoomuchpain(certainlycomparedtothepainofcallbackhell!).

OnceyoucreateaPromiseandregisterafulfillmentand/orrejectionhandlerforit,there'snothingexternalyoucandotostopthatprogressionifsomethingelsehappenstomakethattaskmoot.

Note:ManyPromiseabstractionlibrariesprovidefacilitiestocancelPromises,butthisisaterribleidea!ManydeveloperswishPromiseshadnativelybeendesignedwithexternalcancelationcapability,buttheproblemisthatitwouldletoneconsumer/observerofaPromiseaffectsomeotherconsumer'sabilitytoobservethatsamePromise.Thisviolatesthefuture-value'strustability(externalimmutability),butmoreveristheembodimentofthe"actionatadistance"anti-pattern(http://en.wikipedia.org/wiki/Action_at_a_distance_%28computer_programming%29).Regardlessofhowusefulitseems,itwillactuallyleadyoustraightbackintothesamenightmaresascallbacks.

ConsiderourPromisetimeoutscenariofromearlier:

PromiseUncancelable

Page 76: You Don't Know JS: Async & Performance

varp=foo(42);

Promise.race([

p,

timeoutPromise(3000)

])

.then(

doSomething,

handleError

);

p.then(function(){

//stillhappenseveninthetimeoutcase:(

});

The"timeout"wasexternaltothepromisep,sopitselfkeepsgoing,whichweprobablydon'twant.

Oneoptionistoinvasivelydefineyourresolutioncallbacks:

varOK=true;

varp=foo(42);

Promise.race([

p,

timeoutPromise(3000)

.catch(function(err){

OK=false;

throwerr;

})

])

.then(

doSomething,

handleError

);

p.then(function(){

if(OK){

//onlyhappensifnotimeout!:)

}

});

Thisisugly.Itworks,butit'sfarfromideal.Generally,youshouldtrytoavoidsuchscenarios.

Butifyoucan't,theuglinessofthissolutionshouldbeacluethatcancelationisafunctionalitythatbelongsatahigherlevelofabstractionontopofPromises.I'drecommendyoulooktoPromiseabstractionlibrariesforassistanceratherthanhackingityourself.

Note:MyasynquencePromiseabstractionlibraryprovidesjustsuchanabstractionandanabort()capabilityforthesequence,allofwhichwillbediscussedinAppendixA.

AsinglePromiseisnotreallyaflow-controlmechanism(atleastnotinaverymeaningfulsense),whichisexactlywhatcancelationrefersto;that'swhyPromisecancelationwouldfeelawkward.

Bycontrast,achainofPromisestakencollectivelytogether--whatIliketocalla"sequence"--isaflowcontrolexpression,andthusit'sappropriateforcancelationtobedefinedatthatlevelofabstraction.

NoindividualPromiseshouldbecancelable,butit'ssensibleforasequencetobecancelable,becauseyoudon'tpassaroundasequenceasasingleimmutablevaluelikeyoudowithaPromise.

Thisparticularlimitationisbothsimpleandcomplex.

Comparinghowmanypiecesaremovingwithabasiccallback-basedasynctaskchainversusaPromisechain,it'sclear

PromisePerformance

Page 77: You Don't Know JS: Async & Performance

Promiseshaveafairbitmoregoingon,whichmeanstheyarenaturallyatleastatinybitslower.ThinkbacktojustthesimplelistoftrustguaranteesthatPromisesoffer,ascomparedtotheadhocsolutioncodeyou'dhavetolayerontopofcallbackstoachievethesameprotections.

Moreworktodo,moreguardstoprotect,meansthatPromisesareslowerascomparedtonaked,untrustablecallbacks.Thatmuchisobvious,andprobablysimpletowrapyourbrainaround.

Buthowmuchslower?Well...that'sactuallyprovingtobeanincrediblydifficultquestiontoanswerabsolutely,acrosstheboard.

Frankly,it'skindofanapples-to-orangescomparison,soit'sprobablythewrongquestiontoask.Youshouldactuallycomparewhetheranad-hoccallbacksystemwithallthesameprotectionsmanuallylayeredinisfasterthanaPromiseimplementation.

IfPromiseshavealegitimateperformancelimitation,it'smorethattheydon'treallyofferaline-itemchoiceastowhichtrustabilityprotectionsyouwant/needornot--yougetthemall,always.

Nevertheless,ifwegrantthataPromiseisgenerallyalittlebitslowerthanitsnon-Promise,non-trustablecallbackequivalent--assumingthereareplaceswhereyoufeelyoucanjustifythelackoftrustability--doesthatmeanthatPromisesshouldbeavoidedacrosstheboard,asifyourentireapplicationisdrivenbynothingbutmust-be-utterly-the-fastestcodepossible?

Sanitycheck:ifyourcodeislegitimatelylikethat,isJavaScripteventherightlanguageforsuchtasks?JavaScriptcanbeoptimizedtorunapplicationsveryperformantly(seeChapter5andChapter6).ButisobsessingovertinyperformancetradeoffswithPromises,inlightofallthebenefitstheyoffer,reallyappropriate?

AnothersubtleissueisthatPromisesmakeeverythingasync,whichmeansthatsomeimmediately(synchronously)completestepsstilldeferadvancementofthenextsteptoaJob(seeChapter1).Thatmeansthatit'spossiblethatasequenceofPromisetaskscouldcompleteever-so-slightlyslowerthanthesamesequencewiredupwithcallbacks.

Ofcourse,thequestionhereisthis:arethesepotentialslipsintinyfractionsofperformanceworthalltheotherarticulatedbenefitsofPromiseswe'velaidoutacrossthischapter?

MytakeisthatinvirtuallyallcaseswhereyoumightthinkPromiseperformanceisslowenoughtobeconcerned,it'sactuallyananti-patterntooptimizeawaythebenefitsofPromisetrustabilityandcomposabilitybyavoidingthemaltogether.

Instead,youshoulddefaulttousingthemacrossthecodebase,andthenprofileandanalyzeyourapplication'shot(critical)paths.ArePromisesreallyabottleneck,oraretheyjustatheoreticalslowdown?Onlythen,armedwithactualvalidbenchmarks(seeChapter6)isitresponsibleandprudenttofactoroutthePromisesinjustthoseidentifiedcriticalareas.

Promisesarealittleslower,butinexchangeyou'regettingalotoftrustability,non-Zalgopredictability,andcomposabilitybuiltin.Maybethelimitationisnotactuallytheirperformance,butyourlackofperceptionoftheirbenefits?

Promisesareawesome.Usethem.Theysolvetheinversionofcontrolissuesthatplagueuswithcallbacks-onlycode.

Theydon'tgetridofcallbacks,theyjustredirecttheorchestrationofthosecallbackstoatrustableintermediarymechanismthatsitsbetweenusandanotherutility.

Promisechainsalsobegintoaddress(thoughcertainlynotperfectly)abetterwayofexpressingasyncflowinsequentialfashion,whichhelpsourbrainsplanandmaintainasyncJScodebetter.We'llseeanevenbettersolutiontothatprobleminthenextchapter!

Review

Page 78: You Don't Know JS: Async & Performance

InChapter2,weidentifiedtwokeydrawbackstoexpressingasyncflowcontrolwithcallbacks:

Callback-basedasyncdoesn'tfithowourbrainplansoutstepsofatask.Callbacksaren'ttrustableorcomposablebecauseofinversionofcontrol.

InChapter3,wedetailedhowPromisesuninverttheinversionofcontrolofcallbacks,restoringtrustability/composability.

Nowweturnourattentiontoexpressingasyncflowcontrolinasequential,synchronous-lookingfashion.The"magic"thatmakesitpossibleisES6generators.

InChapter1,weexplainedanexpectationthatJSdevelopersalmostuniversallyrelyonintheircode:onceafunctionstartsexecuting,itrunsuntilitcompletes,andnoothercodecaninterruptandruninbetween.

Asbizarreasitmayseem,ES6introducesanewtypeoffunctionthatdoesnotbehavewiththerun-to-completionbehavior.Thisnewtypeoffunctioniscalleda"generator."

Tounderstandtheimplications,let'sconsiderthisexample:

varx=1;

functionfoo(){

x++;

bar();//<--whataboutthisline?

console.log("x:",x);

}

functionbar(){

x++;

}

foo();//x:3

Inthisexample,weknowforsurethatbar()runsinbetweenx++andconsole.log(x).Butwhatifbar()wasn'tthere?Obviouslytheresultwouldbe2insteadof3.

Nowlet'stwistyourbrain.Whatifbar()wasn'tpresent,butitcouldstillsomehowrunbetweenthex++andconsole.log(x)statements?Howwouldthatbepossible?

Inpreemptivemultithreadedlanguages,itwouldessentiallybepossibleforbar()to"interrupt"andrunatexactlytherightmomentbetweenthosetwostatements.ButJSisnotpreemptive,norisit(currently)multithreaded.Andyet,acooperativeformofthis"interruption"(concurrency)ispossible,iffoo()itselfcouldsomehowindicatea"pause"atthatpartinthecode.

Note:Iusetheword"cooperative"notonlybecauseoftheconnectiontoclassicalconcurrencyterminology(seeChapter1),butbecauseasyou'llseeinthenextsnippet,theES6syntaxforindicatingapausepointincodeisyield--suggestingapolitelycooperativeyieldingofcontrol.

Here'stheES6codetoaccomplishsuchcooperativeconcurrency:

varx=1;

YouDon'tKnowJS:Async&Performance

Chapter4:Generators

BreakingRun-to-Completion

Page 79: You Don't Know JS: Async & Performance

function*foo(){

x++;

yield;//pause!

console.log("x:",x);

}

functionbar(){

x++;

}

Note:YouwilllikelyseemostotherJSdocumentation/codethatwillformatageneratordeclarationasfunction*foo(){..}insteadofasI'vedoneherewithfunction*foo(){..}--theonlydifferencebeingthestylisticpositioningofthe*.Thetwoformsarefunctionally/syntacticallyidentical,asisathirdfunction*foo(){..}(nospace)form.Thereareargumentsforbothstyles,butIbasicallypreferfunction*foo..becauseitthenmatcheswhenIreferenceageneratorinwritingwith*foo().IfIsaidonlyfoo(),youwouldn'tknowasclearlyifIwastalkingaboutageneratororaregularfunction.It'spurelyastylisticpreference.

Now,howcanwerunthecodeinthatprevioussnippetsuchthatbar()executesatthepointoftheyieldinsideof*foo()?

//constructaniterator`it`tocontrolthegenerator

varit=foo();

//start`foo()`here!

it.next();

x;//2

bar();

x;//3

it.next();//x:3

OK,there'squiteabitofnewandpotentiallyconfusingstuffinthosetwocodesnippets,sowe'vegotplentytowadethrough.Butbeforeweexplainthedifferentmechanics/syntaxwithES6generators,let'swalkthroughthebehaviorflow:

1. Theit=foo()operationdoesnotexecutethe*foo()generatoryet,butitmerelyconstructsaniteratorthatwillcontrolitsexecution.Moreoniteratorsinabit.

2. Thefirstit.next()startsthe*foo()generator,andrunsthex++onthefirstlineof*foo().3. *foo()pausesattheyieldstatement,atwhichpointthatfirstit.next()callfinishes.Atthemoment,*foo()isstill

runningandactive,butit'sinapausedstate.4. Weinspectthevalueofx,andit'snow2.5. Wecallbar(),whichincrementsxagainwithx++.6. Weinspectthevalueofxagain,andit'snow3.7. Thefinalit.next()callresumesthe*foo()generatorfromwhereitwaspaused,andrunstheconsole.log(..)

statement,whichusesthecurrentvalueofxof3.

Clearly,foo()started,butdidnotrun-to-completion--itpausedattheyield.Weresumedfoo()later,andletitfinish,butthatwasn'tevenrequired.

So,ageneratorisaspecialkindoffunctionthatcanstartandstoponeormoretimes,anddoesn'tnecessarilyeverhavetofinish.Whileitwon'tbeterriblyobviousyetwhythat'ssopowerful,aswegothroughouttherestofthischapter,thatwillbeoneofthefundamentalbuildingblocksweusetoconstructgenerators-as-async-flow-controlasapatternforourcode.

Ageneratorfunctionisaspecialfunctionwiththenewprocessingmodelwejustalludedto.Butit'sstillafunction,whichmeansitstillhassomebasictenetsthathaven'tchanged--namely,thatitstillacceptsarguments(aka"input"),andthatitcanstillreturnavalue(aka"output"):

function*foo(x,y){

returnx*y;

}

InputandOutput

Page 80: You Don't Know JS: Async & Performance

varit=foo(6,7);

varres=it.next();

res.value;//42

Wepassinthearguments6and7to*foo(..)astheparametersxandy,respectively.And*foo(..)returnsthevalue42backtothecallingcode.

Wenowseeadifferencewithhowthegeneratorisinvokedcomparedtoanormalfunction.foo(6,7)obviouslylooksfamiliar.Butsubtly,the*foo(..)generatorhasn'tactuallyrunyetasitwouldhavewithafunction.

Instead,we'rejustcreatinganiteratorobject,whichweassigntothevariableit,tocontrolthe*foo(..)generator.Thenwecallit.next(),whichinstructsthe*foo(..)generatortoadvancefromitscurrentlocation,stoppingeitheratthenextyieldorendofthegenerator.

Theresultofthatnext(..)callisanobjectwithavaluepropertyonitholdingwhatevervalue(ifanything)wasreturnedfrom*foo(..).Inotherwords,yieldcausedavaluetobesentoutfromthegeneratorduringthemiddleofitsexecution,kindoflikeanintermediatereturn.

Again,itwon'tbeobviousyetwhyweneedthiswholeindirectiteratorobjecttocontrolthegenerator.We'llgetthere,Ipromise.

Inadditiontogeneratorsacceptingargumentsandhavingreturnvalues,there'sevenmorepowerfulandcompellinginput/outputmessagingcapabilitybuiltintothem,viayieldandnext(..).

Consider:

function*foo(x){

vary=x*(yield);

returny;

}

varit=foo(6);

//start`foo(..)`

it.next();

varres=it.next(7);

res.value;//42

First,wepassin6astheparameterx.Thenwecallit.next(),anditstartsup*foo(..).

Inside*foo(..),thevary=x..statementstartstobeprocessed,butthenitrunsacrossayieldexpression.Atthatpoint,itpauses*foo(..)(inthemiddleoftheassignmentstatement!),andessentiallyrequeststhecallingcodetoprovidearesultvaluefortheyieldexpression.Next,wecallit.next(7),whichispassingthe7valuebackintobethatresultofthepausedyieldexpression.

So,atthispoint,theassignmentstatementisessentiallyvary=6*7.Now,returnyreturnsthat42valuebackastheresultoftheit.next(7)call.

Noticesomethingveryimportantbutalsoeasilyconfusing,eventoseasonedJSdevelopers:dependingonyourperspective,there'samismatchbetweentheyieldandthenext(..)call.Ingeneral,you'regoingtohaveonemorenext(..)callthanyouhaveyieldstatements--theprecedingsnippethasoneyieldandtwonext(..)calls.

Whythemismatch?

IterationMessaging

Page 81: You Don't Know JS: Async & Performance

Becausethefirstnext(..)alwaysstartsagenerator,andrunstothefirstyield.Butit'sthesecondnext(..)callthatfulfillsthefirstpausedyieldexpression,andthethirdnext(..)wouldfulfillthesecondyield,andsoon.

Actually,whichcodeyou'rethinkingaboutprimarilywillaffectwhetherthere'saperceivedmismatchornot.

Consideronlythegeneratorcode:

vary=x*(yield);

returny;

Thisfirstyieldisbasicallyaskingaquestion:"WhatvalueshouldIinserthere?"

Who'sgoingtoanswerthatquestion?Well,thefirstnext()hasalreadyruntogetthegeneratoruptothispoint,soobviouslyitcan'tanswerthequestion.So,thesecondnext(..)callmustanswerthequestionposedbythefirstyield.

Seethemismatch--second-to-first?

Butlet'sflipourperspective.Let'slookatitnotfromthegenerator'spointofview,butfromtheiterator'spointofview.

Toproperlyillustratethisperspective,wealsoneedtoexplainthatmessagescangoinbothdirections--yield..asanexpressioncansendoutmessagesinresponsetonext(..)calls,andnext(..)cansendvaluestoapausedyieldexpression.Considerthisslightlyadjustedcode:

function*foo(x){

vary=x*(yield"Hello");//<--yieldavalue!

returny;

}

varit=foo(6);

varres=it.next();//first`next()`,don'tpassanything

res.value;//"Hello"

res=it.next(7);//pass`7`towaiting`yield`

res.value;//42

yield..andnext(..)pairtogetherasatwo-waymessagepassingsystemduringtheexecutionofthegenerator.

So,lookingonlyattheiteratorcode:

varres=it.next();//first`next()`,don'tpassanything

res.value;//"Hello"

res=it.next(7);//pass`7`towaiting`yield`

res.value;//42

Note:Wedon'tpassavaluetothefirstnext()call,andthat'sonpurpose.Onlyapausedyieldcouldacceptsuchavaluepassedbyanext(..),andatthebeginningofthegeneratorwhenwecallthefirstnext(),thereisnopausedyieldtoacceptsuchavalue.Thespecificationandallcompliantbrowsersjustsilentlydiscardanythingpassedtothefirstnext().It'sstillabadideatopassavalue,asyou'rejustcreatingsilently"failing"codethat'sconfusing.So,alwaysstartageneratorwithanargument-freenext().

Thefirstnext()call(withnothingpassedtoit)isbasicallyaskingaquestion:"Whatnextvaluedoesthe*foo(..)generatorhavetogiveme?"Andwhoanswersthisquestion?Thefirstyield"hello"expression.

See?Nomismatchthere.

Dependingonwhoyouthinkaboutaskingthequestion,thereiseitheramismatchbetweentheyieldandnext(..)calls,

TaleofTwoQuestions

Page 82: You Don't Know JS: Async & Performance

ornot.

Butwait!There'sstillanextranext()comparedtothenumberofyieldstatements.So,thatfinalit.next(7)callisagainaskingthequestionaboutwhatnextvaluethegeneratorwillproduce.Butthere'snomoreyieldstatementslefttoanswer,isthere?Sowhoanswers?

Thereturnstatementanswersthequestion!

Andifthereisnoreturninyourgenerator--returniscertainlynotanymorerequiredingeneratorsthaninregularfunctions--there'salwaysanassumed/implicitreturn;(akareturnundefined;),whichservesthepurposeofdefaultansweringthequestionposedbythefinalit.next(7)call.

Thesequestionsandanswers--thetwo-waymessagepassingwithyieldandnext(..)--arequitepowerful,butit'snotobviousatallhowthesemechanismsareconnectedtoasyncflowcontrol.We'regettingthere!

Itmayappearfromthesyntacticusagethatwhenyouuseaniteratortocontrolagenerator,you'recontrollingthedeclaredgeneratorfunctionitself.Butthere'sasubtletythateasytomiss:eachtimeyouconstructaniterator,youareimplicitlyconstructinganinstanceofthegeneratorwhichthatiteratorwillcontrol.

Youcanhavemultipleinstancesofthesamegeneratorrunningatthesametime,andtheycaneveninteract:

function*foo(){

varx=yield2;

z++;

vary=yield(x*z);

console.log(x,y,z);

}

varz=1;

varit1=foo();

varit2=foo();

varval1=it1.next().value;//2<--yield2

varval2=it2.next().value;//2<--yield2

val1=it1.next(val2*10).value;//40<--x:20,z:2

val2=it2.next(val1*5).value;//600<--x:200,z:3

it1.next(val2/2);//y:300

//203003

it2.next(val1/4);//y:10

//200103

Warning:Themostcommonusageofmultipleinstancesofthesamegeneratorrunningconcurrentlyisnotsuchinteractions,butwhenthegeneratorisproducingitsownvalueswithoutinput,perhapsfromsomeindependentlyconnectedresource.We'lltalkmoreaboutvalueproductioninthenextsection.

Let'sbrieflywalkthroughtheprocessing:

1. Bothinstancesof*foo()arestartedatthesametime,andbothnext()callsrevealavalueof2fromtheyield2statements,respectively.

2. val2*10is2*10,whichissentintothefirstgeneratorinstanceit1,sothatxgetsvalue20.zisincrementedfrom1to2,andthen20*2isyieldedout,settingval1to40.

3. val1*5is40*5,whichissentintothesecondgeneratorinstanceit2,sothatxgetsvalue200.zisincrementedagain,from2to3,andthen200*3isyieldedout,settingval2to600.

4. val2/2is600/2,whichissentintothefirstgeneratorinstanceit1,sothatygetsvalue300,thenprintingout203003foritsxyzvalues,respectively.

5. val1/4is40/4,whichissentintothesecondgeneratorinstanceit2,sothatygetsvalue10,thenprintingout200103foritsxyzvalues,respectively.

MultipleIterators

Page 83: You Don't Know JS: Async & Performance

That'sa"fun"exampletorunthroughinyourmind.Didyoukeepitstraight?

Recallthisscenariofromthe"Run-to-completion"sectionofChapter1:

vara=1;

varb=2;

functionfoo(){

a++;

b=b*a;

a=b+3;

}

functionbar(){

b--;

a=8+b;

b=a*2;

}

WithnormalJSfunctions,ofcourseeitherfoo()canruncompletelyfirst,orbar()canruncompletelyfirst,butfoo()cannotinterleaveitsindividualstatementswithbar().So,thereareonlytwopossibleoutcomestotheprecedingprogram.

However,withgenerators,clearlyinterleaving(eveninthemiddleofstatements!)ispossible:

vara=1;

varb=2;

function*foo(){

a++;

yield;

b=b*a;

a=(yieldb)+3;

}

function*bar(){

b--;

yield;

a=(yield8)+b;

b=a*(yield2);

}

Dependingonwhatrespectiveordertheiteratorscontrolling*foo()and*bar()arecalled,theprecedingprogramcouldproduceseveraldifferentresults.Inotherwords,wecanactuallyillustrate(inasortoffake-ishway)thetheoretical"threadedraceconditions"circumstancesdiscussedinChapter1,byinterleavingthetwogeneratorinterationsoverthesamesharedvariables.

First,let'smakeahelpercalledstep(..)thatcontrolsaniterator:

functionstep(gen){

varit=gen();

varlast;

returnfunction(){

//whateveris`yield`edout,just

//senditrightbackinthenexttime!

last=it.next(last).value;

};

}

step(..)initializesageneratortocreateitsititerator,thenreturnsafunctionwhich,whencalled,advancestheiteratorbyonestep.Additionally,thepreviouslyyieldedoutvalueissentrightbackinatthenextstep.So,yield8willjustbecome8andyieldbwilljustbeb(whateveritwasatthetimeofyield).

Interleaving

Page 84: You Don't Know JS: Async & Performance

Now,justforfun,let'sexperimenttoseetheeffectsofinterleavingthesedifferentchunksof*foo()and*bar().We'llstartwiththeboringbasecase,makingsure*foo()totallyfinishesbefore*bar()(justlikewedidinChapter1):

//makesuretoreset`a`and`b`

a=1;

b=2;

vars1=step(foo);

vars2=step(bar);

//run`*foo()`completelyfirst

s1();

s1();

s1();

//nowrun`*bar()`

s2();

s2();

s2();

s2();

console.log(a,b);//1122

Theendresultis11and22,justasitwasintheChapter1version.Nowlet'smixuptheinterleavingorderingandseehowitchangesthefinalvaluesofaandb:

//makesuretoreset`a`and`b`

a=1;

b=2;

vars1=step(foo);

vars2=step(bar);

s2();//b--;

s2();//yield8

s1();//a++;

s2();//a=8+b;

//yield2

s1();//b=b*a;

//yieldb

s1();//a=b+3;

s2();//b=a*2;

BeforeItellyoutheresults,canyoufigureoutwhataandbareaftertheprecedingprogram?Nocheating!

console.log(a,b);//1218

Note:Asanexerciseforthereader,trytoseehowmanyothercombinationsofresultsyoucangetbackrearrangingtheorderofthes1()ands2()calls.Don'tforgetyou'llalwaysneedthrees1()callsandfours2()calls.Recallthediscussionearlieraboutmatchingnext()withyieldforthereasonswhy.

Youalmostcertainlywon'twanttointentionallycreatethislevelofinterleavingconfusion,asitcreatesincrediblydifficulttounderstandcode.Buttheexerciseisinterestingandinstructivetounderstandmoreabouthowmultiplegeneratorscanrunconcurrentlyinthesamesharedscope,becausetherewillbeplaceswherethiscapabilityisquiteuseful.

We'lldiscussgeneratorconcurrencyinmoredetailattheendofthischapter.

Intheprevioussection,wementionedaninterestinguseforgenerators,asawaytoproducevalues.Thisisnotthemainfocusinthischapter,butwe'dberemissifwedidn'tcoverthebasics,especiallybecausethisusecaseisessentiallytheoriginofthename:generators.

Generator'ingValues

Page 85: You Don't Know JS: Async & Performance

We'regoingtotakeaslightdiversionintothetopicofiteratorsforabit,butwe'llcirclebacktohowtheyrelatetogeneratorsandusingageneratortogeneratevalues.

Imagineyou'reproducingaseriesofvalueswhereeachvaluehasadefinablerelationshiptothepreviousvalue.Todothis,you'regoingtoneedastatefulproducerthatremembersthelastvalueitgaveout.

Youcanimplementsomethinglikethatstraightforwardlyusingafunctionclosure(seetheScope&Closurestitleofthisseries):

vargimmeSomething=(function(){

varnextVal;

returnfunction(){

if(nextVal===undefined){

nextVal=1;

}

else{

nextVal=(3*nextVal)+6;

}

returnnextVal;

};

})();

gimmeSomething();//1

gimmeSomething();//9

gimmeSomething();//33

gimmeSomething();//105

Note:ThenextValcomputationlogicherecouldhavebeensimplified,butconceptually,wedon'twanttocalculatethenextvalue(akanextVal)untilthenextgimmeSomething()callhappens,becauseingeneralthatcouldbearesource-leakydesignforproducersofmorepersistentorresource-limitedvaluesthansimplenumbers.

Generatinganarbitrarynumberseriesisn'taterriblyrealisticexample.Butwhatifyouweregeneratingrecordsfromadatasource?Youcouldimaginemuchthesamecode.

Infact,thistaskisaverycommondesignpattern,usuallysolvedbyiterators.Aniteratorisawell-definedinterfaceforsteppingthroughaseriesofvaluesfromaproducer.TheJSinterfaceforiterators,asitisinmostlanguages,istocallnext()eachtimeyouwantthenextvaluefromtheproducer.

Wecouldimplementthestandarditeratorinterfaceforournumberseriesproducer:

varsomething=(function(){

varnextVal;

return{

//neededfor`for..of`loops

[Symbol.iterator]:function(){returnthis;},

//standarditeratorinterfacemethod

next:function(){

if(nextVal===undefined){

nextVal=1;

}

else{

nextVal=(3*nextVal)+6;

}

return{done:false,value:nextVal};

}

};

})();

something.next().value;//1

something.next().value;//9

something.next().value;//33

ProducersandIterators

Page 86: You Don't Know JS: Async & Performance

something.next().value;//105

Note:We'llexplainwhyweneedthe[Symbol.iterator]:..partofthiscodesnippetinthe"Iterables"section.Syntacticallythough,twoES6featuresareatplay.First,the[..]syntaxiscalledacomputedpropertyname(seethethis&ObjectPrototypestitleofthisseries).It'sawayinanobjectliteraldefinitiontospecifyanexpressionandusetheresultofthatexpressionasthenamefortheproperty.Next,Symbol.iteratorisoneofES6'spredefinedspecialSymbolvalues(seetheES6&Beyondtitleofthisbookseries).

Thenext()callreturnsanobjectwithtwoproperties:doneisabooleanvaluesignalingtheiterator'scompletestatus;valueholdstheiterationvalue.

ES6alsoaddsthefor..ofloop,whichmeansthatastandarditeratorcanautomaticallybeconsumedwithnativeloopsyntax:

for(varvofsomething){

console.log(v);

//don'tletthelooprunforever!

if(v>500){

break;

}

}

//1933105321969

Note:Becauseoursomethingiteratoralwaysreturnsdone:false,thisfor..ofloopwouldrunforever,whichiswhyweputthebreakconditionalin.It'stotallyOKforiteratorstobenever-ending,buttherearealsocaseswheretheiteratorwillrunoverafinitesetofvaluesandeventuallyreturnadone:true.

Thefor..ofloopautomaticallycallsnext()foreachiteration--itdoesn'tpassanyvaluesintothenext()--anditwillautomaticallyterminateonreceivingadone:true.It'squitehandyforloopingoverasetofdata.

Ofcourse,youcouldmanuallyloopoveriterators,callingnext()andcheckingforthedone:trueconditiontoknowwhentostop:

for(

varret;

(ret=something.next())&&!ret.done;

){

console.log(ret.value);

//don'tletthelooprunforever!

if(ret.value>500){

break;

}

}

//1933105321969

Note:ThismanualforapproachiscertainlyuglierthantheES6for..ofloopsyntax,butitsadvantageisthatitaffordsyoutheopportunitytopassinvaluestothenext(..)callsifnecessary.

Inadditiontomakingyourowniterators,manybuilt-indatastructuresinJS(asofES6),likearrays,alsohavedefaultiterators:

vara=[1,3,5,7,9];

for(varvofa){

console.log(v);

}

//13579

Thefor..ofloopasksaforitsiterator,andautomaticallyusesittoiterateovera'svalues.

Page 87: You Don't Know JS: Async & Performance

Note:ItmayseemastrangeomissionbyES6,butregularobjectsintentionallydonotcomewithadefaultiteratorthewayarraysdo.Thereasonsgodeeperthanwewillcoverhere.Ifallyouwantistoiterateoverthepropertiesofanobject(withnoparticularguaranteeofordering),Object.keys(..)returnsanarray,whichcanthenbeusedlikefor(varkofObject.keys(obj)){...Suchafor..ofloopoveranobject'skeyswouldbesimilartoafor..inloop,exceptthatObject.keys(..)doesnotincludepropertiesfromthe[[Prototype]]chainwhilefor..indoes(seethethis&ObjectPrototypestitleofthisseries).

Thesomethingobjectinourrunningexampleiscalledaniterator,asithasthenext()methodonitsinterface.Butacloselyrelatedtermisiterable,whichisanobjectthatcontainsaniteratorthatcaniterateoveritsvalues.

AsofES6,thewaytoretrieveaniteratorfromaniterableisthattheiterablemusthaveafunctiononit,withthenamebeingthespecialES6symbolvalueSymbol.iterator.Whenthisfunctioniscalled,itreturnsaniterator.Thoughnotrequired,generallyeachcallshouldreturnafreshnewiterator.

aintheprevioussnippetisaniterable.Thefor..ofloopautomaticallycallsitsSymbol.iteratorfunctiontoconstructaniterator.Butwecouldofcoursecallthefunctionmanually,andusetheiteratoritreturns:

vara=[1,3,5,7,9];

varit=a[Symbol.iterator]();

it.next().value;//1

it.next().value;//3

it.next().value;//5

..

Inthepreviouscodelistingthatdefinedsomething,youmayhavenoticedthisline:

[Symbol.iterator]:function(){returnthis;}

Thatlittlebitofconfusingcodeismakingthesomethingvalue--theinterfaceofthesomethingiterator--alsoaniterable;it'snowbothaniterableandaniterator.Then,wepasssomethingtothefor..ofloop:

for(varvofsomething){

..

}

Thefor..ofloopexpectssomethingtobeaniterable,soitlooksforandcallsitsSymbol.iteratorfunction.Wedefinedthatfunctiontosimplyreturnthis,soitjustgivesitselfback,andthefor..ofloopisnonethewiser.

Let'sturnourattentionbacktogenerators,inthecontextofiterators.Ageneratorcanbetreatedasaproducerofvaluesthatweextractoneatatimethroughaniteratorinterface'snext()calls.

So,ageneratoritselfisnottechnicallyaniterable,thoughit'sverysimilar--whenyouexecutethegenerator,yougetaniteratorback:

function*foo(){..}

varit=foo();

Wecanimplementthesomethinginfinitenumberseriesproducerfromearlierwithagenerator,likethis:

Iterables

GeneratorIterator

Page 88: You Don't Know JS: Async & Performance

function*something(){

varnextVal;

while(true){

if(nextVal===undefined){

nextVal=1;

}

else{

nextVal=(3*nextVal)+6;

}

yieldnextVal;

}

}

Note:Awhile..trueloopwouldnormallybeaverybadthingtoincludeinarealJSprogram,atleastifitdoesn'thaveabreakorreturninit,asitwouldlikelyrunforever,synchronously,andblock/lock-upthebrowserUI.However,inagenerator,suchaloopisgenerallytotallyOKifithasayieldinit,asthegeneratorwillpauseateachiteration,yieldingbacktothemainprogramand/ortotheeventloopqueue.Toputitglibly,"generatorsputthewhile..truebackinJSprogramming!"

That'safairbitcleanerandsimpler,right?Becausethegeneratorpausesateachyield,thestate(scope)ofthefunction*something()iskeptaround,meaningthere'snoneedfortheclosureboilerplatetopreservevariablestateacrosscalls.

Notonlyisitsimplercode--wedon'thavetomakeourowniteratorinterface--itactuallyismorereason-ablecode,becauseitmoreclearlyexpressestheintent.Forexample,thewhile..truelooptellsusthegeneratorisintendedtorunforever--tokeepgeneratingvaluesaslongaswekeepaskingforthem.

Andnowwecanuseourshinynew*something()generatorwithafor..ofloop,andyou'llseeitworksbasicallyidentically:

for(varvofsomething()){

console.log(v);

//don'tletthelooprunforever!

if(v>500){

break;

}

}

//1933105321969

Butdon'tskipoverfor(varvofsomething())..!Wedidn'tjustreferencesomethingasavaluelikeinearlierexamples,butinsteadcalledthe*something()generatortogetitsiteratorforthefor..oflooptouse.

Ifyou'repayingcloseattention,twoquestionsmayarisefromthisinteractionbetweenthegeneratorandtheloop:

Whycouldn'twesayfor(varvofsomething)..?Becausesomethinghereisagenerator,whichisnotaniterable.Wehavetocallsomething()toconstructaproducerforthefor..oflooptoiterateover.Thesomething()callproducesaniterator,butthefor..ofloopwantsaniterable,right?Yep.Thegenerator'siteratoralsohasaSymbol.iteratorfunctiononit,whichbasicallydoesareturnthis,justlikethesomethingiterablewedefinedearlier.Inotherwords,agenerator'siteratorisalsoaniterable!

Inthepreviousexample,itwouldappeartheiteratorinstanceforthe*something()generatorwasbasicallyleftinasuspendedstateforeverafterthebreakintheloopwascalled.

Butthere'sahiddenbehaviorthattakescareofthatforyou."Abnormalcompletion"(i.e.,"earlytermination")ofthefor..ofloop--generallycausedbyabreak,return,oranuncaughtexception--sendsasignaltothegenerator'siteratorforittoterminate.

Note:Technically,thefor..ofloopalsosendsthissignaltotheiteratoratthenormalcompletionoftheloop.Fora

StoppingtheGenerator

Page 89: You Don't Know JS: Async & Performance

generator,that'sessentiallyamootoperation,asthegenerator'siteratorhadtocompletefirstsothefor..ofloopcompleted.However,customiteratorsmightdesiretoreceivethisadditionalsignalfromfor..ofloopconsumers.

Whileafor..ofloopwillautomaticallysendthissignal,youmaywishtosendthesignalmanuallytoaniterator;youdothisbycallingreturn(..).

Ifyouspecifyatry..finallyclauseinsidethegenerator,itwillalwaysberunevenwhenthegeneratorisexternallycompleted.Thisisusefulifyouneedtocleanupresources(databaseconnections,etc.):

function*something(){

try{

varnextVal;

while(true){

if(nextVal===undefined){

nextVal=1;

}

else{

nextVal=(3*nextVal)+6;

}

yieldnextVal;

}

}

//cleanupclause

finally{

console.log("cleaningup!");

}

}

Theearlierexamplewithbreakinthefor..ofloopwilltriggerthefinallyclause.Butyoucouldinsteadmanuallyterminatethegenerator'siteratorinstancefromtheoutsidewithreturn(..):

varit=something();

for(varvofit){

console.log(v);

//don'tletthelooprunforever!

if(v>500){

console.log(

//completethegenerator'siterator

it.return("HelloWorld").value

);

//no`break`neededhere

}

}

//1933105321969

//cleaningup!

//HelloWorld

Whenwecallit.return(..),itimmediatelyterminatesthegenerator,whichofcourserunsthefinallyclause.Also,itsetsthereturnedvaluetowhateveryoupassedintoreturn(..),whichishow"HelloWorld"comesrightbackout.Wealsodon'tneedtoincludeabreaknowbecausethegenerator'siteratorissettodone:true,sothefor..ofloopwillterminateonitsnextiteration.

Generatorsowetheirnamesakemostlytothisconsumingproducedvaluesuse.Butagain,that'sjustoneoftheusesforgenerators,andfranklynoteventhemainonewe'reconcernedwithinthecontextofthisbook.

Butnowthatwemorefullyunderstandsomeofthemechanicsofhowtheywork,wecannextturnourattentiontohowgeneratorsapplytoasyncconcurrency.

Whatdogeneratorshavetodowithasynccodingpatterns,fixingproblemswithcallbacks,andthelike?Let'sgetto

IteratingGeneratorsAsynchronously

Page 90: You Don't Know JS: Async & Performance

answeringthatimportantquestion.

WeshouldrevisitoneofourscenariosfromChapter3.Let'srecallthecallbackapproach:

functionfoo(x,y,cb){

ajax(

"http://some.url.1/?x="+x+"&y="+y,

cb

);

}

foo(11,31,function(err,text){

if(err){

console.error(err);

}

else{

console.log(text);

}

});

Ifwewantedtoexpressthissametaskflowcontrolwithagenerator,wecoulddo:

functionfoo(x,y){

ajax(

"http://some.url.1/?x="+x+"&y="+y,

function(err,data){

if(err){

//throwanerrorinto`*main()`

it.throw(err);

}

else{

//resume`*main()`withreceived`data`

it.next(data);

}

}

);

}

function*main(){

try{

vartext=yieldfoo(11,31);

console.log(text);

}

catch(err){

console.error(err);

}

}

varit=main();

//startitallup!

it.next();

Atfirstglance,thissnippetislonger,andperhapsalittlemorecomplexlooking,thanthecallbacksnippetbeforeit.Butdon'tletthatimpressiongetyouofftrack.Thegeneratorsnippetisactuallymuchbetter!Butthere'salotgoingonforustoexplain.

First,let'slookatthispartofthecode,whichisthemostimportant:

vartext=yieldfoo(11,31);

console.log(text);

Thinkabouthowthatcodeworksforamoment.We'recallinganormalfunctionfoo(..)andwe'reapparentlyabletogetbackthetextfromtheAjaxcall,eventhoughit'sasynchronous.

Howisthatpossible?IfyourecallthebeginningofChapter1,wehadalmostidenticalcode:

Page 91: You Don't Know JS: Async & Performance

vardata=ajax("..url1..");

console.log(data);

Andthatcodedidn'twork!Canyouspotthedifference?It'stheyieldusedinagenerator.

That'sthemagic!That'swhatallowsustohavewhatappearstobeblocking,synchronouscode,butitdoesn'tactuallyblockthewholeprogram;itonlypauses/blocksthecodeinthegeneratoritself.

Inyieldfoo(11,31),firstthefoo(11,31)callismade,whichreturnsnothing(akaundefined),sowe'remakingacalltorequestdata,butwe'reactuallythendoingyieldundefined.That'sOK,becausethecodeisnotcurrentlyrelyingonayieldedvaluetodoanythinginteresting.We'llrevisitthispointlaterinthechapter.

We'renotusingyieldinamessagepassingsensehere,onlyinaflowcontrolsensetopause/block.Actually,itwillhavemessagepassing,butonlyinonedirection,afterthegeneratorisresumed.

So,thegeneratorpausesattheyield,essentiallyaskingthequestion,"whatvalueshouldIreturntoassigntothevariabletext?"Who'sgoingtoanswerthatquestion?

Lookatfoo(..).IftheAjaxrequestissuccessful,wecall:

it.next(data);

That'sresumingthegeneratorwiththeresponsedata,whichmeansthatourpausedyieldexpressionreceivesthatvaluedirectly,andthenasitrestartsthegeneratorcode,thatvaluegetsassignedtothelocalvariabletext.

Prettycool,huh?

Takeastepbackandconsidertheimplications.Wehavetotallysynchronous-lookingcodeinsidethegenerator(otherthantheyieldkeyworditself),buthiddenbehindthescenes,insideoffoo(..),theoperationscancompleteasynchronously.

That'shuge!That'sanearlyperfectsolutiontoourpreviouslystatedproblemwithcallbacksnotbeingabletoexpressasynchronyinasequential,synchronousfashionthatourbrainscanrelateto.

Inessence,weareabstractingtheasynchronyawayasanimplementationdetail,sothatwecanreasonsynchronously/sequentiallyaboutourflowcontrol:"MakeanAjaxrequest,andwhenitfinishesprintouttheresponse."Andofcourse,wejustexpressedtwostepsintheflowcontrol,butthissamecapabililtyextendswithoutbounds,toletusexpresshowevermanystepsweneedto.

Tip:Thisissuchanimportantrealization,justgobackandreadthelastthreeparagraphsagaintoletitsinkin!

Buttheprecedinggeneratorcodehasevenmoregoodnesstoyieldtous.Let'sturnourattentiontothetry..catchinsidethegenerator:

try{

vartext=yieldfoo(11,31);

console.log(text);

}

catch(err){

console.error(err);

}

Howdoesthiswork?Thefoo(..)callisasynchronouslycompleting,anddoesn'ttry..catchfailtocatchasynchronouserrors,aswelookedatinChapter3?

Wealreadysawhowtheyieldletstheassignmentstatementpausetowaitforfoo(..)tofinish,sothatthecompleted

SynchronousErrorHandling

Page 92: You Don't Know JS: Async & Performance

responsecanbeassignedtotext.Theawesomepartisthatthisyieldpausingalsoallowsthegeneratortocatchanerror.Wethrowthaterrorintothegeneratorwiththispartoftheearliercodelisting:

if(err){

//throwanerrorinto`*main()`

it.throw(err);

}

Theyield-pausenatureofgeneratorsmeansthatnotonlydowegetsynchronous-lookingreturnvaluesfromasyncfunctioncalls,butwecanalsosynchronouslycatcherrorsfromthoseasyncfunctioncalls!

Sowe'veseenwecanthrowerrorsintoagenerator,butwhataboutthrowingerrorsoutofagenerator?Exactlyasyou'dexpect:

function*main(){

varx=yield"HelloWorld";

yieldx.toLowerCase();//causeanexception!

}

varit=main();

it.next().value;//HelloWorld

try{

it.next(42);

}

catch(err){

console.error(err);//TypeError

}

Ofcourse,wecouldhavemanuallythrownanerrorwiththrow..insteadofcausinganexception.

Wecanevencatchthesameerrorthatwethrow(..)intothegenerator,essentiallygivingthegeneratorachancetohandleitbutifitdoesn't,theiteratorcodemusthandleit:

function*main(){

varx=yield"HelloWorld";

//nevergetshere

console.log(x);

}

varit=main();

it.next();

try{

//will`*main()`handlethiserror?we'llsee!

it.throw("Oops");

}

catch(err){

//nope,didn'thandleit!

console.error(err);//Oops

}

Synchronous-lookingerrorhandling(viatry..catch)withasynccodeisahugewinforreadabilityandreason-ability.

Inourpreviousdiscussion,weshowedhowgeneratorscanbeiteratedasynchronously,whichisahugestepforwardinsequentialreason-abilityoverthespaghettimessofcallbacks.Butwelostsomethingveryimportant:thetrustabilityandcomposabilityofPromises(seeChapter3)!

Generators+Promises

Page 93: You Don't Know JS: Async & Performance

Don'tworry--wecangetthatback.ThebestofallworldsinES6istocombinegenerators(synchronous-lookingasynccode)withPromises(trustableandcomposable).

Buthow?

RecallfromChapter3thePromise-basedapproachtoourrunningAjaxexample:

functionfoo(x,y){

returnrequest(

"http://some.url.1/?x="+x+"&y="+y

);

}

foo(11,31)

.then(

function(text){

console.log(text);

},

function(err){

console.error(err);

}

);

InourearliergeneratorcodefortherunningAjaxexample,foo(..)returnednothing(undefined),andouriteratorcontrolcodedidn'tcareaboutthatyieldedvalue.

ButherethePromise-awarefoo(..)returnsapromiseaftermakingtheAjaxcall.Thatsuggeststhatwecouldconstructapromisewithfoo(..)andthenyielditfromthegenerator,andthentheiteratorcontrolcodewouldreceivethatpromise.

Butwhatshouldtheiteratordowiththepromise?

Itshouldlistenforthepromisetoresolve(fulfillmentorrejection),andtheneitherresumethegeneratorwiththefulfillmentmessageorthrowanerrorintothegeneratorwiththerejectionreason.

Letmerepeatthat,becauseit'ssoimportant.ThenaturalwaytogetthemostoutofPromisesandgeneratorsistoyieldaPromise,andwirethatPromisetocontrolthegenerator'siterator.

Let'sgiveitatry!First,we'llputthePromise-awarefoo(..)togetherwiththegenerator*main():

functionfoo(x,y){

returnrequest(

"http://some.url.1/?x="+x+"&y="+y

);

}

function*main(){

try{

vartext=yieldfoo(11,31);

console.log(text);

}

catch(err){

console.error(err);

}

}

Themostpowerfulrevelationinthisrefactoristhatthecodeinside*main()didnothavetochangeatall!Insidethegenerator,whatevervaluesareyieldedoutisjustanopaqueimplementationdetail,sowe'renotevenawareit'shappening,nordoweneedtoworryaboutit.

Buthowarewegoingtorun*main()now?Westillhavesomeoftheimplementationplumbingworktodo,toreceiveandwireuptheyieldedpromisesothatitresumesthegeneratoruponresolution.We'llstartbytryingthatmanually:

varit=main();

Page 94: You Don't Know JS: Async & Performance

varp=it.next().value;

//waitforthe`p`promisetoresolve

p.then(

function(text){

it.next(text);

},

function(err){

it.throw(err);

}

);

Actually,thatwasn'tsopainfulatall,wasit?

Thissnippetshouldlookverysimilartowhatwedidearlierwiththemanuallywiredgeneratorcontrolledbytheerror-firstcallback.Insteadofanif(err){it.throw..,thepromisealreadysplitsfulfillment(success)andrejection(failure)forus,butotherwisetheiteratorcontrolisidentical.

Now,we'veglossedoversomeimportantdetails.

Mostimportantly,wetookadvantageofthefactthatweknewthat*main()onlyhadonePromise-awarestepinit.WhatifwewantedtobeabletoPromise-driveageneratornomatterhowmanystepsithas?Wecertainlydon'twanttomanuallywriteoutthePromisechaindifferentlyforeachgenerator!Whatwouldbemuchnicerisiftherewasawaytorepeat(aka"loop"over)theiterationcontrol,andeachtimeaPromisecomesout,waitonitsresolutionbeforecontinuing.

Also,whatifthegeneratorthrowsoutanerror(intentionallyoraccidentally)duringtheit.next(..)call?Shouldwequit,orshouldwecatchitandsenditrightbackin?Similarly,whatifweit.throw(..)aPromiserejectionintothegenerator,butit'snothandled,andcomesrightbackout?

Themoreyoustarttoexplorethispath,themoreyourealize,"wow,it'dbegreatiftherewasjustsomeutilitytodoitforme."Andyou'reabsolutelycorrect.Thisissuchanimportantpattern,andyoudon'twanttogetitwrong(orexhaustyourselfrepeatingitoverandover),soyourbestbetistouseautilitythatisspecificallydesignedtorunPromise-yieldinggeneratorsinthemannerwe'veillustrated.

SeveralPromiseabstractionlibrariesprovidejustsuchautility,includingmyasynquencelibraryanditsrunner(..),whichwillbediscussedinAppendixAofthisbook.

Butforthesakeoflearningandillustration,let'sjustdefineourownstandaloneutilitythatwe'llcallrun(..):

//thankstoBenjaminGruenbaum(@benjamingronGitHub)for

//bigimprovementshere!

functionrun(gen){

varargs=[].slice.call(arguments,1),it;

//initializethegeneratorinthecurrentcontext

it=gen.apply(this,args);

//returnapromiseforthegeneratorcompleting

returnPromise.resolve()

.then(functionhandleNext(value){

//runtothenextyieldedvalue

varnext=it.next(value);

return(functionhandleResult(next){

//generatorhascompletedrunning?

if(next.done){

returnnext.value;

}

//otherwisekeepgoing

else{

returnPromise.resolve(next.value)

.then(

//resumetheasyncloopon

//success,sendingtheresolved

//valuebackintothegenerator

Promise-AwareGeneratorRunner

Page 95: You Don't Know JS: Async & Performance

handleNext,

//if`value`isarejected

//promise,propagateerrorback

//intothegeneratorforitsown

//errorhandling

functionhandleErr(err){

returnPromise.resolve(

it.throw(err)

)

.then(handleResult);

}

);

}

})(next);

});

}

Asyoucansee,it'saquiteabitmorecomplexthanyou'dprobablywanttoauthoryourself,andyouespeciallywouldn'twanttorepeatthiscodeforeachgeneratoryouuse.So,autility/libraryhelperisdefinitelythewaytogo.Nevertheless,Iencourageyoutospendafewminutesstudyingthatcodelistingtogetabettersenseofhowtomanagethegenerator+Promisenegotiation.

Howwouldyouuserun(..)with*main()inourrunningAjaxexample?

function*main(){

//..

}

run(main);

That'sit!Thewaywewiredrun(..),itwillautomaticallyadvancethegeneratoryoupasstoit,asynchronouslyuntilcompletion.

Note:Therun(..)wedefinedreturnsapromisewhichiswiredtoresolveoncethegeneratoriscomplete,orreceiveanuncaughtexceptionifthegeneratordoesn'thandleit.Wedon'tshowthatcapabilityhere,butwe'llcomebacktoitlaterinthechapter.

Theprecedingpattern--generatorsyieldingPromisesthatthencontrolthegenerator'siteratortoadvanceittocompletion--issuchapowerfulandusefulapproach,itwouldbenicerifwecoulddoitwithouttheclutterofthelibraryutilityhelper(akarun(..)).

There'sprobablygoodnewsonthatfront.Atthetimeofthiswriting,there'searlybutstrongsupportforaproposalformoresyntacticadditioninthisrealmforthepost-ES6,ES7-ishtimeframe.Obviously,it'stooearlytoguaranteethedetails,butthere'saprettydecentchanceitwillshakeoutsimilartothefollowing:

functionfoo(x,y){

returnrequest(

"http://some.url.1/?x="+x+"&y="+y

);

}

asyncfunctionmain(){

try{

vartext=awaitfoo(11,31);

console.log(text);

}

catch(err){

console.error(err);

}

}

main();

ES7:asyncandawait?

Page 96: You Don't Know JS: Async & Performance

Asyoucansee,there'snorun(..)call(meaningnoneedforalibraryutility!)toinvokeanddrivemain()--it'sjustcalledasanormalfunction.Also,main()isn'tdeclaredasageneratorfunctionanymore;it'sanewkindoffunction:asyncfunction.Andfinally,insteadofyieldingaPromise,weawaitforittoresolve.

TheasyncfunctionautomaticallyknowswhattodoifyouawaitaPromise--itwillpausethefunction(justlikewithgenerators)untilthePromiseresolves.Wedidn'tillustrateitinthissnippet,butcallinganasyncfunctionlikemain()automaticallyreturnsapromisethat'sresolvedwheneverthefunctionfinishescompletely.

Tip:Theasync/awaitsyntaxshouldlookveryfamiliartoreaderswithexperienceinC#,becauseit'sbasicallyidentical.

Theproposalessentiallycodifiessupportforthepatternwe'vealreadyderived,intoasyntacticmechanism:combiningPromiseswithsync-lookingflowcontrolcode.That'sthebestofbothworldscombined,toeffectivelyaddresspracticallyallofthemajorconcernsweoutlinedwithcallbacks.

ThemerefactthatsuchaES7-ishproposalalreadyexistsandhasearlysupportandenthusiasmisamajorvoteofconfidenceinthefutureimportanceofthisasyncpattern.

Sofar,allwe'vedemonstratedisasingle-stepasyncflowwithPromises+generators.Butreal-worldcodewilloftenhavemanyasyncsteps.

Ifyou'renotcareful,thesync-lookingstyleofgeneratorsmaylullyouintocomplacencywithhowyoustructureyourasyncconcurrency,leadingtosuboptimalperformancepatterns.Sowewanttospendalittletimeexploringtheoptions.

Imagineascenariowhereyouneedtofetchdatafromtwodifferentsources,thencombinethoseresponsestomakeathirdrequest,andfinallyprintoutthelastresponse.WeexploredasimilarscenariowithPromisesinChapter3,butlet'sreconsideritinthecontextofgenerators.

Yourfirstinstinctmightbesomethinglike:

function*foo(){

varr1=yieldrequest("http://some.url.1");

varr2=yieldrequest("http://some.url.2");

varr3=yieldrequest(

"http://some.url.3/?v="+r1+","+r2

);

console.log(r3);

}

//usepreviouslydefined`run(..)`utility

run(foo);

Thiscodewillwork,butinthespecificsofourscenario,it'snotoptimal.Canyouspotwhy?

Becausether1andr2requestscan--andforperformancereasons,should--runconcurrently,butinthiscodetheywillrunsequentially;the"http://some.url.2"URLisn'tAjaxfetcheduntilafterthe"http://some.url.1"requestisfinished.Thesetworequestsareindependent,sothebetterperformanceapproachwouldlikelybetohavethemrunatthesametime.

Buthowexactlywouldyoudothatwithageneratorandyield?Weknowthatyieldisonlyasinglepausepointinthecode,soyoucan'treallydotwopausesatthesametime.

ThemostnaturalandeffectiveansweristobasetheasyncflowonPromises,specificallyontheircapabilitytomanagestateinatime-independentfashion(see"FutureValue"inChapter3).

Thesimplestapproach:

PromiseConcurrencyinGenerators

Page 97: You Don't Know JS: Async & Performance

function*foo(){

//makebothrequests"inparallel"

varp1=request("http://some.url.1");

varp2=request("http://some.url.2");

//waituntilbothpromisesresolve

varr1=yieldp1;

varr2=yieldp2;

varr3=yieldrequest(

"http://some.url.3/?v="+r1+","+r2

);

console.log(r3);

}

//usepreviouslydefined`run(..)`utility

run(foo);

Whyisthisdifferentfromtheprevioussnippet?Lookatwheretheyieldisandisnot.p1andp2arepromisesforAjaxrequestsmadeconcurrently(aka"inparallel").Itdoesn'tmatterwhichonefinishesfirst,becausepromiseswillholdontotheirresolvedstateforaslongasnecessary.

Thenweusetwosubsequentyieldstatementstowaitforandretrievetheresolutionsfromthepromises(intor1andr2,respectively).Ifp1resolvesfirst,theyieldp1resumesfirstthenwaitsontheyieldp2toresume.Ifp2resolvesfirst,itwilljustpatientlyholdontothatresolutionvalueuntilasked,buttheyieldp1willholdonfirst,untilp1resolves.

Eitherway,bothp1andp2willrunconcurrently,andbothhavetofinish,ineitherorder,beforether3=yieldrequest..Ajaxrequestwillbemade.

Ifthatflowcontrolprocessingmodelsoundsfamiliar,it'sbasicallythesameaswhatweidentifiedinChapter3asthe"gate"pattern,enabledbythePromise.all([..])utility.So,wecouldalsoexpresstheflowcontrollikethis:

function*foo(){

//makebothrequests"inparallel,"and

//waituntilbothpromisesresolve

varresults=yieldPromise.all([

request("http://some.url.1"),

request("http://some.url.2")

]);

varr1=results[0];

varr2=results[1];

varr3=yieldrequest(

"http://some.url.3/?v="+r1+","+r2

);

console.log(r3);

}

//usepreviouslydefined`run(..)`utility

run(foo);

Note:AswediscussedinChapter3,wecanevenuseES6destructuringassignmenttosimplifythevarr1=..varr2=..assignments,withvar[r1,r2]=results.

Inotherwords,alloftheconcurrencycapabilitiesofPromisesareavailabletousinthegenerator+Promiseapproach.Soinanyplacewhereyouneedmorethansequentialthis-then-thatasyncflowcontrolsteps,Promisesarelikelyyourbestbet.

Asawordofstylisticcaution,becarefulabouthowmuchPromiselogicyouincludeinsideyourgenerators.Thewholepointofusinggeneratorsforasynchronyinthewaywe'vedescribedistocreatesimple,sequential,sync-lookingcode,andtohideasmuchofthedetailsofasynchronyawayfromthatcodeaspossible.

Promises,Hidden

Page 98: You Don't Know JS: Async & Performance

Forexample,thismightbeacleanerapproach:

//note:normalfunction,notgenerator

functionbar(url1,url2){

returnPromise.all([

request(url1),

request(url2)

]);

}

function*foo(){

//hidethePromise-basedconcurrencydetails

//inside`bar(..)`

varresults=yieldbar(

"http://some.url.1",

"http://some.url.2"

);

varr1=results[0];

varr2=results[1];

varr3=yieldrequest(

"http://some.url.3/?v="+r1+","+r2

);

console.log(r3);

}

//usepreviouslydefined`run(..)`utility

run(foo);

Inside*foo(),it'scleanerandclearerthatallwe'redoingisjustaskingbar(..)togetussomeresults,andwe'llyield-waitonthattohappen.Wedon'thavetocarethatunderthecoversaPromise.all([..])Promisecompositionwillbeusedtomakethathappen.

Wetreatasynchrony,andindeedPromises,asanimplementationdetail.

HidingyourPromiselogicinsideafunctionthatyoumerelycallfromyourgeneratorisespeciallyusefulifyou'regoingtodoasophisticatedseriesflow-control.Forexample:

functionbar(){

Promise.all([

baz(..)

.then(..),

Promise.race([..])

])

.then(..)

}

Thatkindoflogicissometimesrequired,andifyoudumpitdirectlyinsideyourgenerator(s),you'vedefeatedmostofthereasonwhyyouwouldwanttousegeneratorsinthefirstplace.Weshouldintentionallyabstractsuchdetailsawayfromourgeneratorcodesothattheydon'tclutterupthehigherleveltaskexpression.

Beyondcreatingcodethatisbothfunctionalandperformant,youshouldalsostrivetomakecodethatisasreason-ableandmaintainableaspossible.

Note:Abstractionisnotalwaysahealthythingforprogramming--manytimesitcanincreasecomplexityinexchangeforterseness.Butinthiscase,Ibelieveit'smuchhealthierforyourgenerator+Promiseasynccodethanthealternatives.Aswithallsuchadvice,though,payattentiontoyourspecificsituationsandmakeproperdecisionsforyouandyourteam.

Intheprevioussection,weshowedcallingregularfunctionsfrominsideagenerator,andhowthatremainsausefultechniqueforabstractingawayimplementationdetails(likeasyncPromiseflow).Butthemaindrawbackofusinganormal

GeneratorDelegation

Page 99: You Don't Know JS: Async & Performance

functionforthistaskisthatithastobehavebythenormalfunctionrules,whichmeansitcannotpauseitselfwithyieldlikeageneratorcan.

Itmaythenoccurtoyouthatyoumighttrytocallonegeneratorfromanothergenerator,usingourrun(..)helper,suchas:

function*foo(){

varr2=yieldrequest("http://some.url.2");

varr3=yieldrequest("http://some.url.3/?v="+r2);

returnr3;

}

function*bar(){

varr1=yieldrequest("http://some.url.1");

//"delegating"to`*foo()`via`run(..)`

varr3=yieldrun(foo);

console.log(r3);

}

run(bar);

Werun*foo()insideof*bar()byusingourrun(..)utilityagain.Wetakeadvantagehereofthefactthattherun(..)wedefinedearlierreturnsapromisewhichisresolvedwhenitsgeneratorisruntocompletion(orerrorsout),soifweyieldouttoarun(..)instancethepromisefromanotherrun(..)call,itautomaticallypauses*bar()until*foo()finishes.

Butthere'sanevenbetterwaytointegratecalling*foo()into*bar(),andit'scalledyield-delegation.Thespecialsyntaxforyield-delegationis:yield*__(noticetheextra*).Beforeweseeitworkinourpreviousexample,let'slookatasimplerscenario:

function*foo(){

console.log("`*foo()`starting");

yield3;

yield4;

console.log("`*foo()`finished");

}

function*bar(){

yield1;

yield2;

yield*foo();//`yield`-delegation!

yield5;

}

varit=bar();

it.next().value;//1

it.next().value;//2

it.next().value;//`*foo()`starting

//3

it.next().value;//4

it.next().value;//`*foo()`finished

//5

Note:SimilartoanoteearlierinthechapterwhereIexplainedwhyIpreferfunction*foo()..insteadoffunction*foo()..,Ialsoprefer--differingfrommostotherdocumentationonthetopic--tosayyield*foo()insteadofyield*foo().Theplacementofthe*ispurelystylisticanduptoyourbestjudgment.ButIfindtheconsistencyofstylingattractive.

Howdoestheyield*foo()delegationwork?

First,callingfoo()createsaniteratorexactlyaswe'vealreadyseen.Then,yield*delegates/transferstheiteratorinstancecontrol(ofthepresent*bar()generator)overtothisother*foo()iterator.

So,thefirsttwoit.next()callsarecontrolling*bar(),butwhenwemakethethirdit.next()call,now*foo()startsup,andnowwe'recontrolling*foo()insteadof*bar().That'swhyit'scalleddelegation--*bar()delegateditsiteration

Page 100: You Don't Know JS: Async & Performance

controlto*foo().

Assoonastheititeratorcontrolexhauststheentire*foo()iterator,itautomaticallyreturnstocontrolling*bar().

SonowbacktothepreviousexamplewiththethreesequentialAjaxrequests:

function*foo(){

varr2=yieldrequest("http://some.url.2");

varr3=yieldrequest("http://some.url.3/?v="+r2);

returnr3;

}

function*bar(){

varr1=yieldrequest("http://some.url.1");

//"delegating"to`*foo()`via`yield*`

varr3=yield*foo();

console.log(r3);

}

run(bar);

Theonlydifferencebetweenthissnippetandtheversionusedearlieristheuseofyield*foo()insteadofthepreviousyieldrun(foo).

Note:yield*yieldsiterationcontrol,notgeneratorcontrol;whenyouinvokethe*foo()generator,you'renowyield-delegatingtoitsiterator.Butyoucanactuallyyield-delegatetoanyiterable;yield*[1,2,3]wouldconsumethedefaultiteratorforthe[1,2,3]arrayvalue.

Thepurposeofyield-delegationismostlycodeorganization,andinthatwayissymmetricalwithnormalfunctioncalling.

Imaginetwomodulesthatrespectivelyprovidemethodsfoo()andbar(),wherebar()callsfoo().Thereasonthetwoareseparateisgenerallybecausetheproperorganizationofcodefortheprogramcallsforthemtobeinseparatefunctions.Forexample,theremaybecaseswherefoo()iscalledstandalone,andotherplaceswherebar()callsfoo().

Foralltheseexactsamereasons,keepinggeneratorsseparateaidsinprogramreadability,maintenance,anddebuggability.Inthatrespect,yield*isasyntacticshortcutformanuallyiteratingoverthestepsof*foo()whileinsideof*bar().

Suchmanualapproachwouldbeespeciallycomplexifthestepsin*foo()wereasynchronous,whichiswhyyou'dprobablyneedtousethatrun(..)utilitytodoit.Andaswe'veshown,yield*foo()eliminatestheneedforasub-instanceoftherun(..)utility(likerun(foo)).

Youmaywonderhowthisyield-delegationworksnotjustwithiteratorcontrolbutwiththetwo-waymessagepassing.Carefullyfollowtheflowofmessagesinandout,throughtheyield-delegation:

function*foo(){

console.log("inside`*foo()`:",yield"B");

console.log("inside`*foo()`:",yield"C");

return"D";

}

function*bar(){

console.log("inside`*bar()`:",yield"A");

WhyDelegation?

DelegatingMessages

Page 101: You Don't Know JS: Async & Performance

//`yield`-delegation!

console.log("inside`*bar()`:",yield*foo());

console.log("inside`*bar()`:",yield"E");

return"F";

}

varit=bar();

console.log("outside:",it.next().value);

//outside:A

console.log("outside:",it.next(1).value);

//inside`*bar()`:1

//outside:B

console.log("outside:",it.next(2).value);

//inside`*foo()`:2

//outside:C

console.log("outside:",it.next(3).value);

//inside`*foo()`:3

//inside`*bar()`:D

//outside:E

console.log("outside:",it.next(4).value);

//inside`*bar()`:4

//outside:F

Payparticularattentiontotheprocessingstepsaftertheit.next(3)call:

1. The3valueispassed(throughtheyield-delegationin*bar())intothewaitingyield"C"expressioninsideof*foo().

2. *foo()thencallsreturn"D",butthisvaluedoesn'tgetreturnedallthewaybacktotheoutsideit.next(3)call.3. Instead,the"D"valueissentastheresultofthewaitingyield*foo()expressioninsideof*bar()--thisyield-

delegationexpressionhasessentiallybeenpausedwhileallof*foo()wasexhausted.So"D"endsupinsideof*bar()forittoprintout.

4. yield"E"iscalledinsideof*bar(),andthe"E"valueisyieldedtotheoutsideastheresultoftheit.next(3)call.

Fromtheperspectiveoftheexternaliterator(it),itdoesn'tappearanydifferentlybetweencontrollingtheinitialgeneratororadelegatedone.

Infact,yield-delegationdoesn'tevenhavetobedirectedtoanothergenerator;itcanjustbedirectedtoanon-generator,generaliterable.Forexample:

function*bar(){

console.log("inside`*bar()`:",yield"A");

//`yield`-delegationtoanon-generator!

console.log("inside`*bar()`:",yield*["B","C","D"]);

console.log("inside`*bar()`:",yield"E");

return"F";

}

varit=bar();

console.log("outside:",it.next().value);

//outside:A

console.log("outside:",it.next(1).value);

//inside`*bar()`:1

//outside:B

console.log("outside:",it.next(2).value);

//outside:C

console.log("outside:",it.next(3).value);

//outside:D

console.log("outside:",it.next(4).value);

Page 102: You Don't Know JS: Async & Performance

//inside`*bar()`:undefined

//outside:E

console.log("outside:",it.next(5).value);

//inside`*bar()`:5

//outside:F

Noticethedifferencesinwherethemessageswerereceived/reportedbetweenthisexampleandtheoneprevious.

Moststrikingly,thedefaultarrayiteratordoesn'tcareaboutanymessagessentinvianext(..)calls,sothevalues2,3,and4areessentiallyignored.Also,becausethatiteratorhasnoexplicitreturnvalue(unlikethepreviouslyused*foo()),theyield*expressiongetsanundefinedwhenitfinishes.

Inthesamewaythatyield-delegationtransparentlypassesmessagesthroughinbothdirections,errors/exceptionsalsopassinbothdirections:

function*foo(){

try{

yield"B";

}

catch(err){

console.log("errorcaughtinside`*foo()`:",err);

}

yield"C";

throw"D";

}

function*bar(){

yield"A";

try{

yield*foo();

}

catch(err){

console.log("errorcaughtinside`*bar()`:",err);

}

yield"E";

yield*baz();

//note:can'tgethere!

yield"G";

}

function*baz(){

throw"F";

}

varit=bar();

console.log("outside:",it.next().value);

//outside:A

console.log("outside:",it.next(1).value);

//outside:B

console.log("outside:",it.throw(2).value);

//errorcaughtinside`*foo()`:2

//outside:C

console.log("outside:",it.next(3).value);

//errorcaughtinside`*bar()`:D

//outside:E

try{

console.log("outside:",it.next(4).value);

}

catch(err){

console.log("errorcaughtoutside:",err);

}

ExceptionsDelegated,Too!

Page 103: You Don't Know JS: Async & Performance

//errorcaughtoutside:F

Somethingstonotefromthissnippet:

1. Whenwecallit.throw(2),itsendstheerrormessage2into*bar(),whichdelegatesthatto*foo(),whichthencatchesitandhandlesitgracefully.Then,theyield"C"sends"C"backoutasthereturnvaluefromtheit.throw(2)call.

2. The"D"valuethat'snextthrownfrominside*foo()propagatesoutto*bar(),whichcatchesitandhandlesitgracefully.Thentheyield"E"sends"E"backoutasthereturnvaluefromtheit.next(3)call.

3. Next,theexceptionthrownfrom*baz()isn'tcaughtin*bar()--thoughwedidcatchitoutside--soboth*baz()and*bar()aresettoacompletedstate.Afterthissnippet,youwouldnotbeabletogetthe"G"valueoutwithanysubsequentnext(..)call(s)--theywilljustreturnundefinedforvalue.

Let'sfinallygetbacktoourearlieryield-delegationexamplewiththemultiplesequentialAjaxrequests:

function*foo(){

varr2=yieldrequest("http://some.url.2");

varr3=yieldrequest("http://some.url.3/?v="+r2);

returnr3;

}

function*bar(){

varr1=yieldrequest("http://some.url.1");

varr3=yield*foo();

console.log(r3);

}

run(bar);

Insteadofcallingyieldrun(foo)insideof*bar(),wejustcallyield*foo().

Inthepreviousversionofthisexample,thePromisemechanism(controlledbyrun(..))wasusedtotransportthevaluefromreturnr3in*foo()tothelocalvariabler3inside*bar().Now,thatvalueisjustreturnedbackdirectlyviatheyield*mechanics.

Otherwise,thebehaviorisprettymuchidentical.

Ofcourse,yield-delegationcankeepfollowingasmanydelegationstepsasyouwireup.Youcouldevenuseyield-delegationforasync-capablegenerator"recursion"--ageneratoryield-delegatingtoitself:

function*foo(val){

if(val>1){

//generatorrecursion

val=yield*foo(val-1);

}

returnyieldrequest("http://some.url/?v="+val);

}

function*bar(){

varr1=yield*foo(3);

console.log(r1);

}

run(bar);

DelegatingAsynchrony

Delegating"Recursion"

Page 104: You Don't Know JS: Async & Performance

Note:Ourrun(..)utilitycouldhavebeencalledwithrun(foo,3),becauseitsupportsadditionalparametersbeingpassedalongtotheinitializationofthegenerator.However,weusedaparameter-free*bar()heretohighlighttheflexibilityofyield*.

Whatprocessingstepsfollowfromthatcode?Hangon,thisisgoingtobequiteintricatetodescribeindetail:

1. run(bar)startsupthe*bar()generator.2. foo(3)createsaniteratorfor*foo(..)andpasses3asitsvalparameter.3. Because3>1,foo(2)createsanotheriteratorandpassesin2asitsvalparameter.4. Because2>1,foo(1)createsyetanotheriteratorandpassesin1asitsvalparameter.5. 1>1isfalse,sowenextcallrequest(..)withthe1value,andgetapromisebackforthatfirstAjaxcall.6. Thatpromiseisyieldedout,whichcomesbacktothe*foo(2)generatorinstance.7. Theyield*passesthatpromisebackouttothe*foo(3)generatorinstance.Anotheryield*passesthepromise

outtothe*bar()generatorinstance.Andyetagainanotheryield*passesthepromiseouttotherun(..)utility,whichwillwaitonthatpromise(forthefirstAjaxrequest)toproceed.

8. Whenthepromiseresolves,itsfulfillmentmessageissenttoresume*bar(),whichpassesthroughtheyield*intothe*foo(3)instance,whichthenpassesthroughtheyield*tothe*foo(2)generatorinstance,whichthenpassesthroughtheyield*tothenormalyieldthat'swaitinginthe*foo(3)generatorinstance.

9. Thatfirstcall'sAjaxresponseisnowimmediatelyreturnedfromthe*foo(3)generatorinstance,whichsendsthatvaluebackastheresultoftheyield*expressioninthe*foo(2)instance,andassignedtoitslocalvalvariable.

10. Inside*foo(2),asecondAjaxrequestismadewithrequest(..),whosepromiseisyieldedbacktothe*foo(1)instance,andthenyield*propagatesallthewayouttorun(..)(step7again).Whenthepromiseresolves,thesecondAjaxresponsepropagatesallthewaybackintothe*foo(2)generatorinstance,andisassignedtoitslocalvalvariable.

11. Finally,thethirdAjaxrequestismadewithrequest(..),itspromisegoesouttorun(..),andthenitsresolutionvaluecomesallthewayback,whichisthenreturnedsothatitcomesbacktothewaitingyield*expressionin*bar().

Phew!Alotofcrazymentaljuggling,huh?Youmightwanttoreadthroughthatafewmoretimes,andthengograbasnacktoclearyourhead!

AswediscussedinbothChapter1andearlierinthischapter,twosimultaneouslyrunning"processes"cancooperativelyinterleavetheiroperations,andmanytimesthiscanyield(punintended)verypowerfulasynchronyexpressions.

Frankly,ourearlierexamplesofconcurrencyinterleavingofmultiplegeneratorsshowedhowtomakeitreallyconfusing.Butwehintedthatthere'splaceswherethiscapabilityisquiteuseful.

RecallascenariowelookedatinChapter1,wheretwodifferentsimultaneousAjaxresponsehandlersneededtocoordinatewitheachothertomakesurethatthedatacommunicationwasnotaracecondition.Weslottedtheresponsesintotheresarraylikethis:

functionresponse(data){

if(data.url=="http://some.url.1"){

res[0]=data;

}

elseif(data.url=="http://some.url.2"){

res[1]=data;

}

}

Buthowcanweusemultiplegeneratorsconcurrentlyforthisscenario?

//`request(..)`isaPromise-awareAjaxutility

varres=[];

function*reqData(url){

GeneratorConcurrency

Page 105: You Don't Know JS: Async & Performance

res.push(

yieldrequest(url)

);

}

Note:We'regoingtousetwoinstancesofthe*reqData(..)generatorhere,butthere'snodifferencetorunningasingleinstanceoftwodifferentgenerators;bothapproachesarereasonedaboutidentically.We'llseetwodifferentgeneratorscoordinatinginjustabit.

Insteadofhavingtomanuallysortoutres[0]andres[1]assignments,we'llusecoordinatedorderingsothatres.push(..)properlyslotsthevaluesintheexpectedandpredictableorder.Theexpressedlogicthusshouldfeelabitcleaner.

Buthowwillweactuallyorchestratethisinteraction?First,let'sjustdoitmanually,withPromises:

varit1=reqData("http://some.url.1");

varit2=reqData("http://some.url.2");

varp1=it1.next();

varp2=it2.next();

p1

.then(function(data){

it1.next(data);

returnp2;

})

.then(function(data){

it2.next(data);

});

*reqData(..)'stwoinstancesarebothstartedtomaketheirAjaxrequests,thenpausedwithyield.Thenwechoosetoresumethefirstinstancewhenp1resolves,andthenp2'sresolutionwillrestartthesecondinstance.Inthisway,weusePromiseorchestrationtoensurethatres[0]willhavethefirstresponseandres[1]willhavethesecondresponse.

Butfrankly,thisisawfullymanual,anditdoesn'treallyletthegeneratorsorchestratethemselves,whichiswherethetruepowercanlie.Let'stryitadifferentway:

//`request(..)`isaPromise-awareAjaxutility

varres=[];

function*reqData(url){

vardata=yieldrequest(url);

//transfercontrol

yield;

res.push(data);

}

varit1=reqData("http://some.url.1");

varit2=reqData("http://some.url.2");

varp1=it.next();

varp2=it.next();

p1.then(function(data){

it1.next(data);

});

p2.then(function(data){

it2.next(data);

});

Promise.all([p1,p2])

.then(function(){

it1.next();

it2.next();

});

Page 106: You Don't Know JS: Async & Performance

OK,thisisabitbetter(thoughstillmanual!),becausenowthetwoinstancesof*reqData(..)runtrulyconcurrently,and(atleastforthefirstpart)independently.

Intheprevioussnippet,thesecondinstancewasnotgivenitsdatauntilafterthefirstinstancewastotallyfinished.Buthere,bothinstancesreceivetheirdataassoonastheirrespectiveresponsescomeback,andtheneachinstancedoesanotheryieldforcontroltransferpurposes.WethenchoosewhatordertoresumetheminthePromise.all([..])handler.

Whatmaynotbeasobviousisthatthisapproachhintsataneasierformforareusableutility,becauseofthesymmetry.Wecandoevenbetter.Let'simagineusingautilitycalledrunAll(..):

//`request(..)`isaPromise-awareAjaxutility

varres=[];

runAll(

function*(){

varp1=request("http://some.url.1");

//transfercontrol

yield;

res.push(yieldp1);

},

function*(){

varp2=request("http://some.url.2");

//transfercontrol

yield;

res.push(yieldp2);

}

);

Note:We'renotincludingacodelistingforrunAll(..)asitisnotonlylongenoughtobogdownthetext,butisanextensionofthelogicwe'vealreadyimplementedinrun(..)earlier.So,asagoodsupplementaryexerciseforthereader,tryyourhandatevolvingthecodefromrun(..)toworkliketheimaginedrunAll(..).Also,myasynquencelibraryprovidesapreviouslymentionedrunner(..)utilitywiththiskindofcapabilityalreadybuiltin,andwillbediscussedinAppendixAofthisbook.

Here'showtheprocessinginsiderunAll(..)wouldoperate:

1. ThefirstgeneratorgetsapromiseforthefirstAjaxresponsefrom"http://some.url.1",thenyieldscontrolbacktotherunAll(..)utility.

2. Thesecondgeneratorrunsanddoesthesamefor"http://some.url.2",yieldingcontrolbacktotherunAll(..)utility.

3. Thefirstgeneratorresumes,andthenyieldsoutitspromisep1.TherunAll(..)utilitydoesthesameinthiscaseasourpreviousrun(..),inthatitwaitsonthatpromisetoresolve,thenresumesthesamegenerator(nocontroltransfer!).Whenp1resolves,runAll(..)resumesthefirstgeneratoragainwiththatresolutionvalue,andthenres[0]isgivenitsvalue.Whenthefirstgeneratorthenfinishes,that'sanimplicittransferofcontrol.

4. Thesecondgeneratorresumes,yieldsoutitspromisep2,andwaitsforittoresolve.Onceitdoes,runAll(..)resumesthesecondgeneratorwiththatvalue,andres[1]isset.

Inthisrunningexample,weuseanoutervariablecalledrestostoretheresultsofthetwodifferentAjaxresponses--that'sourconcurrencycoordinationmakingthatpossible.

ButitmightbequitehelpfultofurtherextendrunAll(..)toprovideaninnervariablespaceforthemultiplegeneratorinstancestoshare,suchasanemptyobjectwe'llcalldatabelow.Also,itcouldtakenon-Promisevaluesthatareyieldedandhandthemofftothenextgenerator.

Consider:

Page 107: You Don't Know JS: Async & Performance

//`request(..)`isaPromise-awareAjaxutility

runAll(

function*(data){

data.res=[];

//transfercontrol(andmessagepass)

varurl1=yield"http://some.url.2";

varp1=request(url1);//"http://some.url.1"

//transfercontrol

yield;

data.res.push(yieldp1);

},

function*(data){

//transfercontrol(andmessagepass)

varurl2=yield"http://some.url.1";

varp2=request(url2);//"http://some.url.2"

//transfercontrol

yield;

data.res.push(yieldp2);

}

);

Inthisformulation,thetwogeneratorsarenotjustcoordinatingcontroltransfer,butactuallycommunicatingwitheachother,boththroughdata.resandtheyieldedmessagesthattradeurl1andurl2values.That'sincrediblypowerful!

SuchrealizationalsoservesasaconceptualbaseforamoresophisticatedasynchronytechniquecalledCSP(CommunicatingSequentialProcesses),whichwewillcoverinAppendixBofthisbook.

Sofar,we'vemadetheassumptionthatyieldingaPromisefromagenerator--andhavingthatPromiseresumethegeneratorviaahelperutilitylikerun(..)--wasthebestpossiblewaytomanageasynchronywithgenerators.Tobeclear,itis.

Butweskippedoveranotherpatternthathassomemildlywidespreadadoption,sointheinterestofcompletenesswe'lltakeabrieflookatit.

Ingeneralcomputerscience,there'sanoldpre-JSconceptcalleda"thunk."Withoutgettingboggeddowninthehistoricalnature,anarrowexpressionofathunkinJSisafunctionthat--withoutanyparameters--iswiredtocallanotherfunction.

Inotherwords,youwrapafunctiondefinitionaroundfunctioncall--withanyparametersitneeds--todefertheexecutionofthatcall,andthatwrappingfunctionisathunk.Whenyoulaterexecutethethunk,youendupcallingtheoriginalfunction.

Forexample:

functionfoo(x,y){

returnx+y;

}

functionfooThunk(){

returnfoo(3,4);

}

//later

console.log(fooThunk());//7

Thunks

Page 108: You Don't Know JS: Async & Performance

So,asynchronousthunkisprettystraightforward.Butwhataboutanasyncthunk?Wecanessentiallyextendthenarrowthunkdefinitiontoincludeitreceivingacallback.

Consider:

functionfoo(x,y,cb){

setTimeout(function(){

cb(x+y);

},1000);

}

functionfooThunk(cb){

foo(3,4,cb);

}

//later

fooThunk(function(sum){

console.log(sum);//7

});

Asyoucansee,fooThunk(..)onlyexpectsacb(..)parameter,asitalreadyhasvalues3and4(forxandy,respectively)pre-specifiedandreadytopasstofoo(..).Athunkisjustwaitingaroundpatientlyforthelastpieceitneedstodoitsjob:thecallback.

Youdon'twanttomakethunksmanually,though.So,let'sinventautilitythatdoesthiswrappingforus.

Consider:

functionthunkify(fn){

varargs=[].slice.call(arguments,1);

returnfunction(cb){

args.push(cb);

returnfn.apply(null,args);

};

}

varfooThunk=thunkify(foo,3,4);

//later

fooThunk(function(sum){

console.log(sum);//7

});

Tip:Hereweassumethattheoriginal(foo(..))functionsignatureexpectsitscallbackinthelastposition,withanyotherparameterscomingbeforeit.Thisisaprettyubiquitous"standard"forasyncJSfunctionstandards.Youmightcallit"callback-laststyle."Ifforsomereasonyouhadaneedtohandle"callback-firststyle"signatures,youwouldjustmakeautilitythatusedargs.unshift(..)insteadofargs.push(..).

Theprecedingformulationofthunkify(..)takesboththefoo(..)functionreference,andanyparametersitneeds,andreturnsbackthethunkitself(fooThunk(..)).However,that'snotthetypicalapproachyou'llfindtothunksinJS.

Insteadofthunkify(..)makingthethunkitself,typically--ifnotperplexingly--thethunkify(..)utilitywouldproduceafunctionthatproducesthunks.

Uhhhh...yeah.

Consider:

functionthunkify(fn){

returnfunction(){

varargs=[].slice.call(arguments);

returnfunction(cb){

args.push(cb);

Page 109: You Don't Know JS: Async & Performance

returnfn.apply(null,args);

};

};

}

Themaindifferencehereistheextrareturnfunction(){..}layer.Here'showitsusagediffers:

varwhatIsThis=thunkify(foo);

varfooThunk=whatIsThis(3,4);

//later

fooThunk(function(sum){

console.log(sum);//7

});

Obviously,thebigquestionthissnippetimpliesiswhatiswhatIsThisproperlycalled?It'snotthethunk,it'sthethingthatwillproducethunksfromfoo(..)calls.It'skindoflikea"factory"for"thunks."Theredoesn'tseemtobeanykindofstandardagreementfornamingsuchathing.

So,myproposalis"thunkory"("thunk"+"factory").So,thunkify(..)producesathunkory,andathunkoryproducesthunks.Thatreasoningissymmetrictomyproposalfor"promisory"inChapter3:

varfooThunkory=thunkify(foo);

varfooThunk1=fooThunkory(3,4);

varfooThunk2=fooThunkory(5,6);

//later

fooThunk1(function(sum){

console.log(sum);//7

});

fooThunk2(function(sum){

console.log(sum);//11

});

Note:Therunningfoo(..)exampleexpectsastyleofcallbackthat'snot"error-firststyle."Ofcourse,"error-firststyle"ismuchmorecommon.Iffoo(..)hadsomesortoflegitimateerror-producingexpectation,wecouldchangeittoexpectanduseanerror-firstcallback.Noneofthesubsequentthunkify(..)machinerycareswhatstyleofcallbackisassumed.TheonlydifferenceinusagewouldbefooThunk1(function(err,sum){...

Exposingthethunkorymethod--insteadofhowtheearlierthunkify(..)hidesthisintermediarystep--mayseemlikeunnecessarycomplication.Butingeneral,it'squiteusefultomakethunkoriesatthebeginningofyourprogramtowrapexistingAPImethods,andthenbeabletopassaroundandcallthosethunkorieswhenyouneedthunks.Thetwodistinctstepspreserveacleanerseparationofcapability.

Toillustrate:

//cleaner:

varfooThunkory=thunkify(foo);

varfooThunk1=fooThunkory(3,4);

varfooThunk2=fooThunkory(5,6);

//insteadof:

varfooThunk1=thunkify(foo,3,4);

varfooThunk2=thunkify(foo,5,6);

Regardlessofwhetheryouliketodealwiththethunkoriesexplicitlyornot,theusageofthunksfooThunk1(..)andfooThunk2(..)remainsthesame.

Page 110: You Don't Know JS: Async & Performance

Sowhat'sallthisthunkstuffhavetodowithgenerators?

Comparingthunkstopromisesgenerally:they'renotdirectlyinterchangableasthey'renotequivalentinbehavior.Promisesarevastlymorecapableandtrustablethanbarethunks.

Butinanothersense,theybothcanbeseenasarequestforavalue,whichmaybeasyncinitsanswering.

RecallfromChapter3wedefinedautilityforpromisifyingafunction,whichwecalledPromise.wrap(..)--wecouldhavecalleditpromisify(..),too!ThisPromise-wrappingutilitydoesn'tproducePromises;itproducespromisoriesthatinturnproducePromises.Thisiscompletelysymmetrictothethunkoriesandthunkspresentlybeingdiscussed.

Toillustratethesymmetry,let'sfirstaltertherunningfoo(..)examplefromearliertoassumean"error-firststyle"callback:

functionfoo(x,y,cb){

setTimeout(function(){

//assume`cb(..)`as"error-firststyle"

cb(null,x+y);

},1000);

}

Now,we'llcompareusingthunkify(..)andpromisify(..)(akaPromise.wrap(..)fromChapter3):

//symmetrical:constructingthequestionasker

varfooThunkory=thunkify(foo);

varfooPromisory=promisify(foo);

//symmetrical:askingthequestion

varfooThunk=fooThunkory(3,4);

varfooPromise=fooPromisory(3,4);

//getthethunkanswer

fooThunk(function(err,sum){

if(err){

console.error(err);

}

else{

console.log(sum);//7

}

});

//getthepromiseanswer

fooPromise

.then(

function(sum){

console.log(sum);//7

},

function(err){

console.error(err);

}

);

Boththethunkoryandthepromisoryareessentiallyaskingaquestion(foravalue),andrespectivelythethunkfooThunkandpromisefooPromiserepresentthefutureanswerstothatquestion.Presentedinthatlight,thesymmetryisclear.

Withthatperspectiveinmind,wecanseethatgeneratorswhichyieldPromisesforasynchronycouldinsteadyieldthunksforasynchrony.Allwe'dneedisasmarterrun(..)utility(likefrombefore)thatcannotonlylookforandwireuptoayieldedPromisebutalsotoprovideacallbacktoayieldedthunk.

Consider:

function*foo(){

varval=yieldrequest("http://some.url.1");

console.log(val);

s/promise/thunk/

Page 111: You Don't Know JS: Async & Performance

}

run(foo);

Inthisexample,request(..)couldeitherbeapromisorythatreturnsapromise,orathunkorythatreturnsathunk.Fromtheperspectiveofwhat'sgoingoninsidethegeneratorcodelogic,wedon'tcareaboutthatimplementationdetail,whichisquitepowerful!

So,request(..)couldbeeither:

//promisory`request(..)`(seeChapter3)

varrequest=Promise.wrap(ajax);

//vs.

//thunkory`request(..)`

varrequest=thunkify(ajax);

Finally,asathunk-awarepatchtoourearlierrun(..)utility,wewouldneedlogiclikethis:

//..

//didwereceiveathunkback?

elseif(typeofnext.value=="function"){

returnnewPromise(function(resolve,reject){

//callthethunkwithanerror-firstcallback

next.value(function(err,msg){

if(err){

reject(err);

}

else{

resolve(msg);

}

});

})

.then(

handleNext,

functionhandleErr(err){

returnPromise.resolve(

it.throw(err)

)

.then(handleResult);

}

);

}

Now,ourgeneratorscaneithercallpromisoriestoyieldPromises,orcallthunkoriestoyieldthunks,andineithercase,run(..)wouldhandlethatvalueanduseittowaitforthecompletiontoresumethegenerator.

Symmetrywise,thesetwoapproacheslookidentical.However,weshouldpointoutthat'strueonlyfromtheperspectiveofPromisesorthunksrepresentingthefuturevaluecontinuationofagenerator.

Fromthelargerperspective,thunksdonotinandofthemselveshavehardlyanyofthetrustabilityorcomposabilityguaranteesthatPromisesaredesignedwith.Usingathunkasastand-inforaPromiseinthisparticulargeneratorasynchronypatternisworkablebutshouldbeseenaslessthanidealwhencomparedtoallthebenefitsthatPromisesoffer(seeChapter3).

Ifyouhavetheoption,preferyieldprratherthanyieldth.Butthere'snothingwrongwithhavingarun(..)utilitywhichcanhandlebothvaluetypes.

Note:Therunner(..)utilityinmyasynquencelibrary,whichwillbediscussedinAppendixA,handlesyieldsofPromises,thunksandasynquencesequences.

Pre-ES6Generators

Page 112: You Don't Know JS: Async & Performance

You'rehopefullyconvincednowthatgeneratorsareaveryimportantadditiontotheasyncprogrammingtoolbox.Butit'sanewsyntaxinES6,whichmeansyoucan'tjustpolyfillgeneratorslikeyoucanPromises(whicharejustanewAPI).SowhatcanwedotobringgeneratorstoourbrowserJSifwedon'thavetheluxuryofignoringpre-ES6browsers?

ForallnewsyntaxextensionsinES6,therearetools--themostcommontermforthemistranspilers,fortrans-compilers--whichcantakeyourES6syntaxandtransformitintoequivalent(butobviouslyuglier!)pre-ES6code.So,generatorscanbetranspiledintocodethatwillhavethesamebehaviorbutworkinES5andbelow.

Buthow?The"magic"ofyielddoesn'tobviouslysoundlikecodethat'seasytotranspile.Weactuallyhintedatasolutioninourearlierdiscussionofclosure-basediterators.

Beforewediscussthetranspilers,let'sderivehowmanualtranspilationwouldworkinthecaseofgenerators.Thisisn'tjustanacademicexercise,becausedoingsowillactuallyhelpfurtherreinforcehowtheywork.

Consider:

//`request(..)`isaPromise-awareAjaxutility

function*foo(url){

try{

console.log("requesting:",url);

varval=yieldrequest(url);

console.log(val);

}

catch(err){

console.log("Oops:",err);

returnfalse;

}

}

varit=foo("http://some.url.1");

Thefirstthingtoobserveisthatwe'llstillneedanormalfoo()functionthatcanbecalled,anditwillstillneedtoreturnaniterator.So,let'ssketchoutthenon-generatortransformation:

functionfoo(url){

//..

//makeandreturnaniterator

return{

next:function(v){

//..

},

throw:function(e){

//..

}

};

}

varit=foo("http://some.url.1");

Thenextthingtoobserveisthatageneratordoesits"magic"bysuspendingitsscope/state,butwecanemulatethatwithfunctionclosure(seetheScope&Closurestitleofthisseries).Tounderstandhowtowritesuchcode,we'llfirstannotatedifferentpartsofourgeneratorwithstatevalues:

//`request(..)`isaPromise-awareAjaxutility

function*foo(url){

//STATE*1*

try{

console.log("requesting:",url);

ManualTransformation

Page 113: You Don't Know JS: Async & Performance

varTMP1=request(url);

//STATE*2*

varval=yieldTMP1;

console.log(val);

}

catch(err){

//STATE*3*

console.log("Oops:",err);

returnfalse;

}

}

Note:Formoreaccurateillustration,wesplituptheval=yieldrequest..statementintotwoparts,usingthetemporaryTMP1variable.request(..)happensinstate*1*,andtheassignmentofitscompletionvaluetovalhappensinstate*2*.We'llgetridofthatintermediateTMP1whenweconvertthecodetoitsnon-generatorequivalent.

Inotherwords,*1*isthebeginningstate,*2*isthestateiftherequest(..)succeeds,and*3*isthestateiftherequest(..)fails.Youcanprobablyimaginehowanyextrayieldstepswouldjustbeencodedasextrastates.

Backtoourtranspiledgenerator,let'sdefineavariablestateintheclosurewecanusetokeeptrackofthestate:

functionfoo(url){

//managegeneratorstate

varstate;

//..

}

Now,let'sdefineaninnerfunctioncalledprocess(..)insidetheclosurewhichhandleseachstate,usingaswitchstatement:

//`request(..)`isaPromise-awareAjaxutility

functionfoo(url){

//managegeneratorstate

varstate;

//generator-widevariabledeclarations

varval;

functionprocess(v){

switch(state){

case1:

console.log("requesting:",url);

returnrequest(url);

case2:

val=v;

console.log(val);

return;

case3:

varerr=v;

console.log("Oops:",err);

returnfalse;

}

}

//..

}

Eachstateinourgeneratorisrepresentedbyitsowncaseintheswitchstatement.process(..)willbecalledeachtimeweneedtoprocessanewstate.We'llcomebacktohowthatworksinjustamoment.

Foranygenerator-widevariabledeclarations(val),wemovethosetoavardeclarationoutsideofprocess(..)sotheycansurvivemultiplecallstoprocess(..).Butthe"blockscoped"errvariableisonlyneededforthe*3*state,soweleaveitinplace.

Page 114: You Don't Know JS: Async & Performance

Instate*1*,insteadofyieldresolve(..),wedidreturnresolve(..).Interminalstate*2*,therewasnoexplicitreturn,sowejustdoareturn;whichisthesameasreturnundefined.Interminalstate*3*,therewasareturnfalse,sowepreservethat.

Nowweneedtodefinethecodeintheiteratorfunctionssotheycallprocess(..)appropriately:

functionfoo(url){

//managegeneratorstate

varstate;

//generator-widevariabledeclarations

varval;

functionprocess(v){

switch(state){

case1:

console.log("requesting:",url);

returnrequest(url);

case2:

val=v;

console.log(val);

return;

case3:

varerr=v;

console.log("Oops:",err);

returnfalse;

}

}

//makeandreturnaniterator

return{

next:function(v){

//initialstate

if(!state){

state=1;

return{

done:false,

value:process()

};

}

//yieldresumedsuccessfully

elseif(state==1){

state=2;

return{

done:true,

value:process(v)

};

}

//generatoralreadycompleted

else{

return{

done:true,

value:undefined

};

}

},

"throw":function(e){

//theonlyexpliciterrorhandlingisin

//state*1*

if(state==1){

state=3;

return{

done:true,

value:process(e)

};

}

//otherwise,anerrorwon'tbehandled,

//sojustthrowitrightbackout

else{

throwe;

}

}

};

}

Howdoesthiscodework?

Page 115: You Don't Know JS: Async & Performance

1. Thefirstcalltotheiterator'snext()callwouldmovethegeneratorfromtheunitializedstatetostate1,andthencallprocess()tohandlethatstate.Thereturnvaluefromrequest(..),whichisthepromisefortheAjaxresponse,isreturnedbackasthevaluepropertyfromthenext()call.

2. IftheAjaxrequestsucceeds,thesecondcalltonext(..)shouldsendintheAjaxresponsevalue,whichmovesourstateto2.process(..)isagaincalled(thistimewiththepassedinAjaxresponsevalue),andthevaluepropertyreturnedfromnext(..)willbeundefined.

3. However,iftheAjaxrequestfails,throw(..)shouldbecalledwiththeerror,whichwouldmovethestatefrom1to3(insteadof2).Againprocess(..)iscalled,thistimewiththeerrorvalue.Thatcasereturnsfalse,whichissetasthevaluepropertyreturnedfromthethrow(..)call.

Fromtheoutside--thatis,interactingonlywiththeiterator--thisfoo(..)normalfunctionworksprettymuchthesameasthe*foo(..)generatorwouldhaveworked.Sowe'veeffectively"transpiled"ourES6generatortopre-ES6compatibility!

Wecouldthenmanuallyinstantiateourgeneratorandcontrolitsiterator--callingvarit=foo("..")andit.next(..)andsuch--orbetter,wecouldpassittoourpreviouslydefinedrun(..)utilityasrun(foo,"..").

TheprecedingexerciseofmanuallyderivingatransformationofourES6generatortopre-ES6equivalentteachesushowgeneratorsworkconceptually.Butthattransformationwasreallyintricateandverynon-portabletoothergeneratorsinourcode.Itwouldbequiteimpracticaltodothisworkbyhand,andwouldcompletelyobviateallthebenefitofgenerators.

Butluckily,severaltoolsalreadyexistthatcanautomaticallyconvertES6generatorstothingslikewhatwederivedintheprevioussection.Notonlydotheydotheheavyliftingworkforus,buttheyalsohandleseveralcomplicationsthatweglossedover.

Onesuchtoolisregenerator(https://facebook.github.io/regenerator/),fromthesmartfolksatFacebook.

Ifweuseregeneratortotranspileourpreviousgenerator,here'sthecodeproduced(atthetimeofthiswriting):

//`request(..)`isaPromise-awareAjaxutility

varfoo=regeneratorRuntime.mark(functionfoo(url){

varval;

returnregeneratorRuntime.wrap(functionfoo$(context$1$0){

while(1)switch(context$1$0.prev=context$1$0.next){

case0:

context$1$0.prev=0;

console.log("requesting:",url);

context$1$0.next=4;

returnrequest(url);

case4:

val=context$1$0.sent;

console.log(val);

context$1$0.next=12;

break;

case8:

context$1$0.prev=8;

context$1$0.t0=context$1$0.catch(0);

console.log("Oops:",context$1$0.t0);

returncontext$1$0.abrupt("return",false);

case12:

case"end":

returncontext$1$0.stop();

}

},foo,this,[[0,8]]);

});

There'ssomeobvioussimilaritiesheretoourmanualderivation,suchastheswitch/casestatements,andweevenseevalpulledoutoftheclosurejustaswedid.

Ofcourse,onetrade-offisthatregenerator'stranspilationrequiresahelperlibraryregeneratorRuntimethatholdsallthereusablelogicformanagingageneralgenerator/iterator.Alotofthatboilerplatelooksdifferentthanourversion,buteven

AutomaticTranspilation

Page 116: You Don't Know JS: Async & Performance

then,theconceptscanbeseen,likewithcontext$1$0.next=4keepingtrackofthenextstateforthegenerator.

ThemaintakeawayisthatgeneratorsarenotrestrictedtoonlybeingusefulinES6+environments.Onceyouunderstandtheconcepts,youcanemploythemthroughoutyourcode,andusetoolstotransformthecodetobecompatiblewitholderenvironments.

ThisismoreworkthanjustusingaPromiseAPIpolyfillforpre-ES6Promises,buttheeffortistotallyworthit,becausegeneratorsaresomuchbetteratexpressingasyncflowcontrolinareason-able,sensible,synchronous-looking,sequentialfashion.

Onceyougethookedongenerators,you'llneverwanttogobacktothehellofasyncspaghetticallbacks!

GeneratorsareanewES6functiontypethatdoesnotrun-to-completionlikenormalfunctions.Instead,thegeneratorcanbepausedinmid-completion(entirelypreservingitsstate),anditcanlaterberesumedfromwhereitleftoff.

Thispause/resumeinterchangeiscooperativeratherthanpreemptive,whichmeansthatthegeneratorhasthesolecapabilitytopauseitself,usingtheyieldkeyword,andyettheiteratorthatcontrolsthegeneratorhasthesolecapability(vianext(..))toresumethegenerator.

Theyield/next(..)dualityisnotjustacontrolmechanism,it'sactuallyatwo-waymessagepassingmechanism.Ayield..expressionessentiallypauseswaitingforavalue,andthenextnext(..)callpassesavalue(orimplicitundefined)backtothatpausedyieldexpression.

Thekeybenefitofgeneratorsrelatedtoasyncflowcontrolisthatthecodeinsideageneratorexpressesasequenceofstepsforthetaskinanaturallysync/sequentialfashion.Thetrickisthatweessentiallyhidepotentialasynchronybehindtheyieldkeyword--movingtheasynchronytothecodewherethegenerator'siteratoriscontrolled.

Inotherwords,generatorspreserveasequential,synchronous,blockingcodepatternforasynccode,whichletsourbrainsreasonaboutthecodemuchmorenaturally,addressingoneofthetwokeydrawbacksofcallback-basedasync.

Review

Page 117: You Don't Know JS: Async & Performance

Thisbooksofarhasbeenallabouthowtoleverageasynchronypatternsmoreeffectively.Butwehaven'tdirectlyaddressedwhyasynchronyreallymatterstoJS.Themostobviousexplicitreasonisperformance.

Forexample,ifyouhavetwoAjaxrequeststomake,andthey'reindependent,butyouneedtowaitonthembothtofinishbeforedoingthenexttask,youhavetwooptionsformodelingthatinteraction:serialandconcurrent.

Youcouldmakethefirstrequestandwaittostartthesecondrequestuntilthefirstfinishes.Or,aswe'veseenbothwithpromisesandgenerators,youcouldmakebothrequests"inparallel,"andexpressthe"gate"towaitonbothofthembeforemovingon.

Clearly,thelatterisusuallygoingtobemoreperformantthantheformer.Andbetterperformancegenerallyleadstobetteruserexperience.

It'sevenpossiblethatasynchrony(interleavedconcurrency)canimprovejusttheperceptionofperformance,eveniftheoverallprogramstilltakesthesameamountoftimetocomplete.Userperceptionofperformanceiseverybit--ifnotmore!--asimportantasactualmeasurableperformance.

Wewanttonowmovebeyondlocalizedasynchronypatternstotalkaboutsomebiggerpictureperformancedetailsattheprogramlevel.

Note:Youmaybewonderingaboutmicro-performanceissueslikeifa++or++aisfaster.We'lllookatthosesortsofperformancedetailsinthenextchapteron"Benchmarking&Tuning."

Ifyouhaveprocessing-intensivetasksbutyoudon'twantthemtorunonthemainthread(whichmayslowdownthebrowser/UI),youmighthavewishedthatJavaScriptcouldoperateinamultithreadedmanner.

InChapter1,wetalkedindetailabouthowJavaScriptissinglethreaded.Andthat'sstilltrue.Butasinglethreadisn'ttheonlywaytoorganizetheexecutionofyourprogram.

Imaginesplittingyourprogramintotwopieces,andrunningoneofthosepiecesonthemainUIthread,andrunningtheotherpieceonanentirelyseparatethread.

Whatkindsofconcernswouldsuchanarchitecturebringup?

Forone,you'dwanttoknowifrunningonaseparatethreadmeantthatitraninparallel(onsystemswithmultipleCPUs/cores)suchthatalong-runningprocessonthatsecondthreadwouldnotblockthemainprogramthread.Otherwise,"virtualthreading"wouldn'tbeofmuchbenefitoverwhatwealreadyhaveinJSwithasyncconcurrency.

Andyou'dwanttoknowifthesetwopiecesoftheprogramhaveaccesstothesamesharedscope/resources.Iftheydo,thenyouhaveallthequestionsthatmultithreadedlanguages(Java,C++,etc.)dealwith,suchasneedingcooperativeorpreemptivelocking(mutexes,etc.).That'salotofextrawork,andshouldn'tbeundertakenlightly.

Alternatively,you'dwanttoknowhowthesetwopiecescould"communicate"iftheycouldn'tsharescope/resources.

AllthesearegreatquestionstoconsiderasweexploreafeatureaddedtothewebplatformcircaHTML5called"WebWorkers."Thisisafeatureofthebrowser(akahostenvironment)andactuallyhasalmostnothingtodowiththeJSlanguageitself.Thatis,JavaScriptdoesnotcurrentlyhaveanyfeaturesthatsupportthreadedexecution.

YouDon'tKnowJS:Async&Performance

Chapter5:ProgramPerformance

WebWorkers

Page 118: You Don't Know JS: Async & Performance

ButanenvironmentlikeyourbrowsercaneasilyprovidemultipleinstancesoftheJavaScriptengine,eachonitsownthread,andletyourunadifferentprogramineachthread.Eachofthoseseparatethreadedpiecesofyourprogramiscalleda"(Web)Worker."Thistypeofparallelismiscalled"taskparallelism,"astheemphasisisonsplittingupchunksofyourprogramtoruninparallel.

FromyourmainJSprogram(oranotherWorker),youinstantiateaWorkerlikeso:

varw1=newWorker("http://some.url.1/mycoolworker.js");

TheURLshouldpointtothelocationofaJSfile(notanHTMLpage!)whichisintendedtobeloadedintoaWorker.Thebrowserwillthenspinupaseparatethreadandletthatfilerunasanindependentprograminthatthread.

Note:ThekindofWorkercreatedwithsuchaURLiscalleda"DedicatedWorker."ButinsteadofprovidingaURLtoanexternalfile,youcanalsocreatean"InlineWorker"byprovidingaBlobURL(anotherHTML5feature);essentiallyit'saninlinefilestoredinasingle(binary)value.However,Blobsarebeyondthescopeofwhatwe'lldiscusshere.

Workersdonotshareanyscopeorresourceswitheachotherorthemainprogram--thatwouldbringallthenightmaresoftheadedprogrammingtotheforefront--butinsteadhaveabasiceventmessagingmechanismconnectingthem.

Thew1Workerobjectisaneventlistenerandtrigger,whichletsyousubscribetoeventssentbytheWorkeraswellassendeventstotheWorker.

Here'showtolistenforevents(actually,thefixed"message"event):

w1.addEventListener("message",function(evt){

//evt.data

});

Andyoucansendthe"message"eventtotheWorker:

w1.postMessage("somethingcooltosay");

InsidetheWorker,themessagingistotallysymmetrical:

//"mycoolworker.js"

addEventListener("message",function(evt){

//evt.data

});

postMessage("areallycoolreply");

NoticethatadedicatedWorkerisinaone-to-onerelationshipwiththeprogramthatcreatedit.Thatis,the"message"eventdoesn'tneedanydisambiguationhere,becausewe'resurethatitcouldonlyhavecomefromthisone-to-onerelationship--eitheritcamefromtheWorkerorthemainpage.

UsuallythemainpageapplicationcreatestheWorkers,butaWorkercaninstantiateitsownchildWorker(s)--knownassubworkers--asnecessary.Sometimesthisisusefultodelegatesuchdetailstoasortof"master"WorkerthatspawnsotherWorkerstoprocesspartsofatask.Unfortunately,atthetimeofthiswriting,Chromestilldoesnotsupportsubworkers,whileFirefoxdoes.

TokillaWorkerimmediatelyfromtheprogramthatcreatedit,callterminate()ontheWorkerobject(likew1intheprevioussnippets).AbruptlyterminatingaWorkerthreaddoesnotgiveitanychancetofinishupitsworkorcleanupanyresources.It'sakintoyouclosingabrowsertabtokillapage.

Page 119: You Don't Know JS: Async & Performance

Ifyouhavetwoormorepages(ormultipletabswiththesamepage!)inthebrowserthattrytocreateaWorkerfromthesamefileURL,thosewillactuallyendupascompletelyseparateWorkers.Shortly,we'lldiscussawayto"share"aWorker.

Note:ItmayseemlikeamaliciousorignorantJSprogramcouldeasilyperformadenial-of-serviceattackonasystembyspawninghundredsofWorkers,seeminglyeachwiththeirownthread.Whileit'struethatit'ssomewhatofaguaranteethataWorkerwillenduponaseparatethread,thisguaranteeisnotunlimited.Thesystemisfreetodecidehowmanyactualthreads/CPUs/coresitreallywantstocreate.There'snowaytopredictorguaranteehowmanyyou'llhaveaccessto,thoughmanypeopleassumeit'satleastasmanyasthenumberofCPUs/coresavailable.Ithinkthesafestassumptionisthatthere'satleastoneotherthreadbesidesthemainUIthread,butthat'saboutit.

InsidetheWorker,youdonothaveaccesstoanyofthemainprogram'sresources.Thatmeansyoucannotaccessanyofitsglobalvariables,norcanyouaccessthepage'sDOMorotherresources.Remember:it'satotallyseparatethread.

Youcan,however,performnetworkoperations(Ajax,WebSockets)andsettimers.Also,theWorkerhasaccesstoitsowncopyofseveralimportantglobalvariables/features,includingnavigator,location,JSON,andapplicationCache.

YoucanalsoloadextraJSscriptsintoyourWorker,usingimportScripts(..):

//insidetheWorker

importScripts("foo.js","bar.js");

Thesescriptsareloadedsynchronously,whichmeanstheimportScripts(..)callwillblocktherestoftheWorker'sexecutionuntilthefile(s)arefinishedloadingandexecuting.

Note:Therehavealsobeensomediscussionsaboutexposingthe<canvas>APItoWorkers,whichcombinedwithhavingcanvasesbeTransferables(seethe"DataTransfer"section),wouldallowWorkerstoperformmoresophisticatedoff-threadgraphicsprocessing,whichcanbeusefulforhigh-performancegaming(WebGL)andothersimilarapplications.Althoughthisdoesn'texistyetinanybrowsers,it'slikelytohappeninthenearfuture.

WhataresomecommonusesforWebWorkers?

ProcessingintensivemathcalculationsSortinglargedatasetsDataoperations(compression,audioanalysis,imagepixelmanipulations,etc.)High-trafficnetworkcommunications

Youmaynoticeacommoncharacteristicofmostofthoseuses,whichisthattheyrequirealargeamountofinformationtobetransferredacrossthebarrierbetweenthreadsusingtheeventmechanism,perhapsinbothdirections.

IntheearlydaysofWorkers,serializingalldatatoastringvaluewastheonlyoption.Inadditiontothespeedpenaltyofthetwo-wayserializations,theothermajornegativewasthatthedatawasbeingcopied,whichmeantadoublingofmemoryusage(andthesubsequentchurnofgarbagecollection).

Thankfully,wenowhaveafewbetteroptions.

Ifyoupassanobject,aso-called"StructuredCloningAlgorithm"(https://developer.mozilla.org/en-US/docs/Web/Guide/API/DOM/The_structured_clone_algorithm)isusedtocopy/duplicatetheobjectontheotherside.Thisalgorithmisfairlysophisticatedandcanevenhandleduplicatingobjectswithcircularreferences.Theto-string/from-stringperformancepenaltyisnotpaid,butwestillhaveduplicationofmemoryusingthisapproach.ThereissupportforthisinIE10andabove,aswellasalltheothermajorbrowsers.

Anevenbetteroption,especiallyforlargerdatasets,is"TransferableObjects"(http://updates.html5rocks.com/2011/12/Transferable-Objects-Lightning-Fast).Whathappensisthattheobject's

WorkerEnvironment

DataTransfer

Page 120: You Don't Know JS: Async & Performance

"ownership"istransferred,butthedataitselfisnotmoved.OnceyoutransferawayanobjecttoaWorker,it'semptyorinaccessibleinthetheoriginatinglocation--thateliminatesthehazardsofthreadedprogrammingoverasharedscope.Ofcourse,transferofownershipcangoinbothdirections.

Therereallyisn'tmuchyouneedtodotooptintoaTransferableObject;anydatastructurethatimplementstheTransferableinterface(https://developer.mozilla.org/en-US/docs/Web/API/Transferable)willautomaticallybetransferredthisway(supportFirefox&Chrome).

Forexample,typedarrayslikeUint8Array(seetheES6&Beyondtitleofthisseries)are"Transferables."Thisishowyou'dsendaTransferableObjectusingpostMessage(..):

//`foo`isa`Uint8Array`forinstance

postMessage(foo.buffer,[foo.buffer]);

Thefirstparameteristherawbufferandthesecondparameterisalistofwhattotransfer.

Browsersthatdon'tsupportTransferableObjectssimplydegradetostructuredcloning,whichmeansperformancereductionratherthanoutrightfeaturebreakage.

Ifyoursiteorappallowsforloadingmultipletabsofthesamepage(acommonfeature),youmayverywellwanttoreducetheresourceusageoftheirsystembypreventingduplicatededicatedWorkers;themostcommonlimitedresourceinthisrespectisasocketnetworkconnection,asbrowserslimitthenumberofsimultaneousconnectionstoasinglehost.Ofcourse,limitingmultipleconnectionsfromaclientalsoeasesyourserverresourcerequirements.

Inthiscase,creatingasinglecentralizedWorkerthatallthepageinstancesofyoursiteorappcanshareisquiteuseful.

That'scalledaSharedWorker,whichyoucreatelikeso(supportforthisislimitedtoFirefoxandChrome):

varw1=newSharedWorker("http://some.url.1/mycoolworker.js");

BecauseasharedWorkercanbeconnectedtoorfrommorethanoneprograminstanceorpageonyoursite,theWorkerneedsawaytoknowwhichprogramamessagecomesfrom.Thisuniqueidentificationiscalleda"port"--thinknetworksocketports.SothecallingprogrammustusetheportobjectoftheWorkerforcommunication:

w1.port.addEventListener("message",handleMessages);

//..

w1.port.postMessage("somethingcool");

Also,theportconnectionmustbeinitialized,as:

w1.port.start();

InsidethesharedWorker,anextraeventmustbehandled:"connect".Thiseventprovidestheportobjectforthatparticularconnection.Themostconvenientwaytokeepmultipleconnectionsseparateistouseclosure(seeScope&Closurestitleofthisseries)overtheport,asshownnext,withtheeventlisteningandtransmittingforthatconnectiondefinedinsidethehandlerforthe"connect"event:

//insidethesharedWorker

addEventListener("connect",function(evt){

//theassignedportforthisconnection

SharedWorkers

Page 121: You Don't Know JS: Async & Performance

varport=evt.ports[0];

port.addEventListener("message",function(evt){

//..

port.postMessage(..);

//..

});

//initializetheportconnection

port.start();

});

Otherthanthatdifference,sharedanddedicatedWorkershavethesamecapabilitiesandsemantics.

Note:SharedWorkerssurvivetheterminationofaportconnectionifotherportconnectionsarestillalive,whereasdedicatedWorkersareterminatedwhenevertheconnectiontotheirinitiatingprogramisterminated.

WebWorkersareveryattractiveperformance-wiseforrunningJSprogramsinparallel.However,youmaybeinapositionwhereyourcodeneedstoruninolderbrowsersthatlacksupport.BecauseWorkersareanAPIandnotasyntax,theycanbepolyfilled,toanextent.

Ifabrowserdoesn'tsupportWorkers,there'ssimplynowaytofakemultithreadingfromtheperformanceperspective.Iframesarecommonlythoughtoftoprovideaparallelenvironment,butinallmodernbrowserstheyactuallyrunonthesamethreadasthemainpage,sothey'renotsufficientforfakingparallelism.

AswedetailedinChapter1,JS'sasynchronicity(notparallelism)comesfromtheeventloopqueue,soyoucanforcefakedWorkerstobeasynchronoususingtimers(setTimeout(..),etc.).ThenyoujustneedtoprovideapolyfillfortheWorkerAPI.Therearesomelistedhere(https://github.com/Modernizr/Modernizr/wiki/HTML5-Cross-Browser-Polyfills#web-workers),butfranklynoneofthemlookgreat.

I'vewrittenasketchofapolyfillforWorkerhere(https://gist.github.com/getify/1b26accb1a09aa53ad25).It'sbasic,butitshouldgetthejobdoneforsimpleWorkersupport,giventhatthetwo-waymessagingworkscorrectlyaswellas"onerror"handling.Youcouldprobablyalsoextenditwithmorefeatures,suchasterminate()orfakedSharedWorkers,asyouseefit.

Note:Youcan'tfakesynchronousblocking,sothispolyfilljustdisallowsuseofimportScripts(..).AnotheroptionmighthavebeentoparseandtransformtheWorker'scode(onceAjaxloaded)tohandlerewritingtosomeasynchronousformofanimportScripts(..)polyfill,perhapswithapromise-awareinterface.

Singleinstruction,multipledata(SIMD)isaformof"dataparallelism,"ascontrastedto"taskparallelism"withWebWorkers,becausetheemphasisisnotreallyonprogramlogicchunksbeingparallelized,butrathermultiplebitsofdatabeingprocessedinparallel.

WithSIMD,threadsdon'tprovidetheparallelism.Instead,modernCPUsprovideSIMDcapabilitywith"vectors"ofnumbers--think:typespecializedarrays--aswellasinstructionsthatcanoperateinparallelacrossallthenumbers;thesearelow-leveloperationsleveraginginstruction-levelparallelism.

TheefforttoexposeSIMDcapabilitytoJavaScriptisprimarilyspearheadedbyIntel(https://01.org/node/1495),namelybyMohammadHaghighat(atthetimeofthiswriting),incooperationwithFirefoxandChrometeams.SIMDisonanearlystandardstrackwithagoodchanceofmakingitintoafuturerevisionofJavaScript,likelyintheES7timeframe.

SIMDJavaScriptproposestoexposeshortvectortypesandAPIstoJScode,whichonthoseSIMD-enabledsystemswouldmaptheoperationsdirectlythroughtotheCPUequivalents,withfallbacktonon-parallelizedoperation"shims"onnon-SIMDsystems.

PolyfillingWebWorkers

SIMD

Page 122: You Don't Know JS: Async & Performance

Theperformancebenefitsfordata-intensiveapplications(signalanalysis,matrixoperationsongraphics,etc.)withsuchparallelmathprocessingarequiteobvious!

EarlyproposalformsoftheSIMDAPIatthetimeofthiswritinglooklikethis:

varv1=SIMD.float32x4(3.14159,21.0,32.3,55.55);

varv2=SIMD.float32x4(2.1,3.2,4.3,5.4);

varv3=SIMD.int32x4(10,101,1001,10001);

varv4=SIMD.int32x4(10,20,30,40);

SIMD.float32x4.mul(v1,v2);//[6.597339,67.2,138.89,299.97]

SIMD.int32x4.add(v3,v4);//[20,121,1031,10041]

Shownherearetwodifferentvectordatatypes,32-bitfloating-pointnumbersand32-bitintegernumbers.Youcanseethatthesevectorsaresizedexactlytofour32-bitelements,asthismatchestheSIMDvectorsizes(128-bit)availableinmostmodernCPUs.It'salsopossiblewemayseeanx8(orlarger!)versionoftheseAPIsinthefuture.

Besidesmul()andadd(),manyotheroperationsarelikelytobeincluded,suchassub(),div(),abs(),neg(),sqrt(),reciprocal(),reciprocalSqrt()(arithmetic),shuffle()(rearrangevectorelements),and(),or(),xor(),not()(logical),equal(),greaterThan(),lessThan()(comparison),shiftLeft(),shiftRightLogical(),shiftRightArithmetic()(shifts),fromFloat32x4(),andfromInt32x4()(conversions).

Note:There'sanofficial"prollyfill"(hopeful,expectant,future-leaningpolyfill)fortheSIMDfunctionalityavailable(https://github.com/johnmccutchan/ecmascript_simd),whichillustratesalotmoreoftheplannedSIMDcapabilitythanwe'veillustratedinthissection.

"asm.js"(http://asmjs.org/)isalabelforahighlyoptimizablesubsetoftheJavaScriptlanguage.Bycarefullyavoidingcertainmechanismsandpatternsthatarehardtooptimize(garbagecollection,coercion,etc.),asm.js-styledcodecanberecognizedbytheJSengineandgivenspecialattentionwithaggressivelow-leveloptimizations.

Distinctfromotherprogramperfomancemechanismsdiscussedinthischapter,asm.jsisn'tnecessarilysomethingthatneedstobeadoptedintotheJSlanguagespecification.Thereisanasm.jsspecification(http://asmjs.org/spec/latest/),butit'smostlyfortrackinganagreeduponsetofcandidateinferencesforoptimizationratherthanasetofrequirementsofJSengines.

There'snotcurrentlyanynewsyntaxbeingproposed.Instead,asm.jssuggestswaystorecognizeexistingstandardJSsyntaxthatconformstotherulesofasm.jsandletenginesimplementtheirownoptimizationsaccordingly.

There'sbeensomedisagreementbetweenbrowservendorsoverexactlyhowasm.jsshouldbeactivatedinaprogram.Earlyversionsoftheasm.jsexperimentrequireda"useasm";pragma(similartostrictmode's"usestrict";)tohelpcluetheJSenginetobelookingforasm.jsoptimizationopportunitiesandhints.Othershaveassertedthatasm.jsshouldjustbeasetofheuristicsthatenginesautomaticallyrecognizewithouttheauthorhavingtodoanythingextra,meaningthatexistingprogramscouldtheoreticallybenefitfromasm.js-styleoptimizationswithoutdoinganythingspecial.

Thefirstthingtounderstandaboutasm.jsoptimizationsisaroundtypesandcoercion(seetheTypes&Grammartitleofthisseries).IftheJSenginehastotrackmultipledifferenttypesofvaluesinavariablethroughvariousoperations,sothatitcanhandlecoercionsbetweentypesasnecessary,that'salotofextraworkthatkeepstheprogramoptimizationsuboptimal.

Note:We'regoingtouseasm.js-stylecodehereforillustrationpurposes,butbeawarethatit'snotcommonlyexpectedthatyou'llauthorsuchcodebyhand.asm.jsismoreintendedtoacompliationtargetfromothertools,suchasEmscripten(https://github.com/kripken/emscripten/wiki).It'sofcoursepossibletowriteyourownasm.jscode,butthat'susuallyabadideabecausethecodeisverylowlevelandmanagingitcanbeverytimeconsuminganderrorprone.Nevertheless,there

asm.js

HowtoOptimizewithasm.js

Page 123: You Don't Know JS: Async & Performance

maybecaseswhereyou'dwanttohandtweakyourcodeforasm.jsoptimizationpurposes.

Therearesome"tricks"youcanusetohinttoanasm.js-awareJSenginewhattheintendedtypeisforvariables/operations,sothatitcanskipthesecoerciontrackingsteps.

Forexample:

vara=42;

//..

varb=a;

Inthatprogram,theb=aassignmentleavesthedooropenfortypedivergenceinvariables.However,itcouldinsteadbewrittenas:

vara=42;

//..

varb=a|0;

Here,we'veusedthe|("binaryOR")withvalue0,whichhasnoeffectonthevalueotherthantomakesureit'sa32-bitinteger.ThatcoderuninanormalJSengineworksjustfine,butwhenruninanasm.js-awareJSengineitcansignalthatbshouldalwaysbetreatedasa32-bitinteger,sothecoerciontrackingcanbeskipped.

Similarly,theadditionoperationbetweentwovariablescanberestrictedtoamoreperformantintegeraddition(insteadoffloatingpoint):

(a+b)|0

Again,theasm.js-awareJSenginecanseethathintandinferthatthe+operationshouldbe32-bitintegeradditionbecausetheendresultofthewholeexpressionwouldautomaticallybe32-bitintegerconformedanyway.

OneofthebiggestdetractorstoperformanceinJSisaroundmemoryallocation,garbagecollection,andscopeaccess.asm.jssuggestsoneofthewaysaroundtheseissuesistodeclareamoreformalizedasm.js"module"--donotconfusethesewithES6modules;seetheES6&Beyondtitleofthisseries.

Foranasm.jsmodule,youneedtoexplicitlypassinatightlyconformednamespace--thisisreferredtointhespecasstdlib,asitshouldrepresentstandardlibrariesneeded--toimportnecessarysymbols,ratherthanjustusingglobalsvialexicalscope.Inthebasecase,thewindowobjectisanacceptablestdlibobjectforasm.jsmodulepurposes,butyoucouldandperhapsshouldconstructanevenmorerestrictedone.

Youalsomustdeclarea"heap"--whichisjustafancytermforareservedspotinmemorywherevariablescanalreadybeusedwithoutaskingformorememoryorreleasingpreviouslyusedmemory--andpassthatin,sothattheasm.jsmodulewon'tneedtodoanythingthatwouldcausememorychurn;itcanjustusethepre-reservedspace.

A"heap"islikelyatypedArrayBuffer,suchas:

varheap=newArrayBuffer(0x10000);//64kheap

Usingthatpre-reserved64kofbinaryspace,anasm.jsmodulecanstoreandretrievevaluesinthatbufferwithoutanymemoryallocationorgarbagecollectionpenalties.Forexample,theheapbuffercouldbeusedinsidethemoduletoback

asm.jsModules

Page 124: You Don't Know JS: Async & Performance

anarrayof64-bitfloatvalueslikethis:

vararr=newFloat64Array(heap);

OK,solet'smakeaquick,sillyexampleofanasm.js-styledmoduletoillustratehowthesepiecesfittogether.We'lldefineafoo(..)thattakesastart(x)andend(y)integerforarange,andcalculatesalltheinneradjacentmultiplicationsofthevaluesintherange,andthenfinallyaveragesthosevaluestogether:

functionfooASM(stdlib,foreign,heap){

"useasm";

vararr=newstdlib.Int32Array(heap);

functionfoo(x,y){

x=x|0;

y=y|0;

vari=0;

varp=0;

varsum=0;

varcount=((y|0)-(x|0))|0;

//calculatealltheinneradjacentmultiplications

for(i=x|0;

(i|0)<(y|0);

p=(p+8)|0,i=(i+1)|0

){

//storeresult

arr[p>>3]=(i*(i+1))|0;

}

//calculateaverageofallintermediatevalues

for(i=0,p=0;

(i|0)<(count|0);

p=(p+8)|0,i=(i+1)|0

){

sum=(sum+arr[p>>3])|0;

}

return+(sum/count);

}

return{

foo:foo

};

}

varheap=newArrayBuffer(0x1000);

varfoo=fooASM(window,null,heap).foo;

foo(10,20);//233

Note:Thisasm.jsexampleishandauthoredforillustrationpurposes,soitdoesn'trepresentthesamecodethatwouldbeproducedfromacompilationtooltargetingasm.js.Butitdoesshowthetypicalnatureofasm.jscode,especiallythetypehintinganduseoftheheapbufferfortemporaryvariablestorage.

ThefirstcalltofooASM(..)iswhatsetsupourasm.jsmodulewithitsheapallocation.Theresultisafoo(..)functionwecancallasmanytimesasnecessary.Thosefoo(..)callsshouldbespeciallyoptimizedbyanasm.js-awareJSengine.Importantly,theprecedingcodeiscompletelystandardJSandwouldrunjustfine(withoutspecialoptimization)inanon-asm.jsengine.

Obviously,thenatureofrestrictionsthatmakeasm.jscodesooptimizablereducesthepossibleusesforsuchcodesignificantly.asm.jswon'tnecessarilybeageneraloptimizationsetforanygivenJSprogram.Instead,it'sintendedtoprovideanoptimizedwayofhandlingspecializedtaskssuchasintensivemathoperations(e.g.,thoseusedingraphicsprocessingforgames).

Review

Page 125: You Don't Know JS: Async & Performance

Thefirstfourchaptersofthisbookarebasedonthepremisethatasynccodingpatternsgiveyoutheabilitytowritemoreperformantcode,whichisgenerallyaveryimportantimprovement.Butasyncbehavioronlygetsyousofar,becauseit'sstillfundamentallyboundtoasingleeventloopthread.

Sointhischapterwe'vecoveredseveralprogram-levelmechanismsforimprovingperformanceevenfurther.

WebWorkersletyourunaJSfile(akaprogram)inaseparatethreadusingasynceventstomessagebetweenthethreads.They'rewonderfulforoffloadinglong-runningorresource-intensivetaskstoadifferentthread,leavingthemainUIthreadmoreresposive.

SIMDproposestomapCPU-levelparallelmathoperationstoJavaScriptAPIsforhigh-performancedata-paralleloperations,likenumberprocessingonlargedatasets.

Finally,asm.jsdescribesasmallsubsetofJavaScriptthatavoidsthehard-to-optimizepartsofJS(likegarbagecollectionandcoercion)andletstheJSenginerecognizeandrunsuchcodethroughaggressiveoptimizations.asm.jscouldbehandauthored,butthat'sextremelytediousanderrorprone,akintohandauthoringassemblylanguage(hencethename).Instead,themainintentisthatasm.jswouldbeagoodtargetforcross-compilationfromotherhighlyoptimizedprogramlanguages--forexample,Emscripten(https://github.com/kripken/emscripten/wiki)transpilingC/C++toJavaScript.

Whilenotcoveredexplicitlyinthischapter,thereareevenmoreradicalideasunderveryearlydiscussionforJavaScript,includingapproximationsofdirectthreadedfunctionality(notjusthiddenbehinddatastructureAPIs).Whetherthathappensexplicitly,orwejustseemoreparallelismcreepintoJSbehindthescenes,thefutureofmoreoptimizedprogram-levelperformanceinJSlooksreallypromising.

Page 126: You Don't Know JS: Async & Performance

Asthefirstfourchaptersofthisbookwereallaboutperformanceasacodingpattern(asynchronyandconcurrency),andChapter5wasaboutperformanceatthemacroprogramarchitecturelevel,thischaptergoesafterthetopicofperformanceatthemicrolevel,focusingonsingleexpressions/statements.

Oneofthemostcommonareasofcuriosity--indeed,somedeveloperscangetquiteobsessedaboutit--isinanalyzingandtestingvariousoptionsforhowtowritealineorchunkofcode,andwhichoneisfaster.

We'regoingtolookatsomeoftheseissues,butit'simportanttounderstandfromtheoutsetthatthischapterisnotaboutfeedingtheobsessionofmicro-performancetuning,likewhethersomegivenJSenginecanrun++afasterthana++.ThemoreimportantgoalofthischapteristofigureoutwhatkindsofJSperformancematterandwhichonesdon't,andhowtotellthedifference.

Butevenbeforewegetthere,weneedtoexplorehowtomostaccuratelyandreliablytestJSperformance,becausethere'stonsofmisconceptionsandmythsthathavefloodedourcollectivecultknowledgebase.We'vegottosiftthroughallthatjunktofindsomeclarity.

OK,timetostartdispellingsomemisconceptions.I'dwagerthevastmajorityofJSdevelopers,ifaskedtobenchmarkthespeed(executiontime)ofacertainoperation,wouldinitiallygoaboutitsomethinglikethis:

varstart=(newDate()).getTime();//or`Date.now()`

//dosomeoperation

varend=(newDate()).getTime();

console.log("Duration:",(end-start));

Raiseyourhandifthat'sroughlywhatcametoyourmind.Yep,Ithoughtso.There'salotwrongwiththisapproach,butdon'tfeelbad;we'veallbeenthere.

Whatdidthatmeasurementtellyou,exactly?Understandingwhatitdoesanddoesn'tsayabouttheexecutiontimeoftheoperationinquestioniskeytolearninghowtoappropriatelybenchmarkperformanceinJavaScript.

Ifthedurationreportedis0,youmaybetemptedtobelievethatittooklessthanamillisecond.Butthat'snotveryaccurate.Someplatformsdon'thavesinglemillisecondprecision,butinsteadonlyupdatethetimerinlargerincrements.Forexample,olderversionsofwindows(andthusIE)hadonly15msprecision,whichmeanstheoperationhastotakeatleastthatlongforanythingotherthan0tobereported!

Moreover,whateverdurationisreported,theonlythingyoureallyknowisthattheoperationtookapproximatelythatlongonthatexactsinglerun.Youhavenear-zeroconfidencethatitwillalwaysrunatthatspeed.Youhavenoideaiftheengineorsystemhadsomesortofinterferenceatthatexactmoment,andthatatothertimestheoperationcouldrunfaster.

Whatifthedurationreportedis4?Areyoumoresureittookaboutfourmilliseconds?Nope.Itmighthavetakenlesstime,andtheremayhavebeensomeotherdelayingettingeitherstartorendtimestamps.

Moretroublingly,youalsodon'tknowthatthecircumstancesofthisoperationtestaren'toverlyoptimistic.It'spossiblethattheJSenginefiguredoutawaytooptimizeyourisolatedtestcase,butinamorerealprogramsuchoptimizationwouldbedilutedorimpossible,suchthattheoperationwouldrunslowerthanyourtest.

YouDon'tKnowJS:Async&Performance

Chapter6:Benchmarking&Tuning

Benchmarking

Page 127: You Don't Know JS: Async & Performance

So...whatdoweknow?Unfortunately,withthoserealizationsstated,weknowverylittle.Somethingofsuchlowconfidenceisn'tevenremotelygoodenoughtobuildyourdeterminationson.Your"benchmark"isbasicallyuseless.Andworse,it'sdangerousinthatitimpliesfalseconfidence,notjusttoyoubutalsotootherswhodon'tthinkcriticallyabouttheconditionsthatledtothoseresults.

"OK,"younowsay,"Justputalooparounditsothewholetesttakeslonger."Ifyourepeatanoperation100times,andthatwholeloopreportedlytakesatotalof137ms,thenyoucanjustdivideby100andgetanaveragedurationof1.37msforeachoperation,right?

Well,notexactly.

Astraightmathematicalaveragebyitselfisdefinitelynotsufficientformakingjudgmentsaboutperformancewhichyouplantoextrapolatetothebreadthofyourentireapplication.Withahundrediterations,evenacoupleofoutliers(highorlow)canskewtheaverage,andthenwhenyouapplythatconclusionrepeatedly,youevenfurtherinflatetheskewbeyondcredulity.

Insteadofjustrunningforafixednumberofiterations,youcaninsteadchoosetoruntheloopoftestsuntilacertainamountoftimehaspassed.Thatmightbemorereliable,buthowdoyoudecidehowlongtorun?Youmightguessthatitshouldbesomemultipleofhowlongyouroperationshouldtaketorunonce.Wrong.

Actually,thelengthoftimetorepeatacrossshouldbebasedontheaccuracyofthetimeryou'reusing,specificallytominimizethechancesofinaccuracy.Thelesspreciseyourtimer,thelongeryouneedtoruntomakesureyou'veminimizedtheerrorpercentage.A15mstimerisprettybadforaccuratebenchmarking;tominimizeitsuncertainty(aka"errorrate")tolessthan1%,youneedtorunyoureachcycleoftestiterationsfor750ms.A1mstimeronlyneedsacycletorunfor50mstogetthesameconfidence.

Butthen,that'sjustasinglesample.Tobesureyou'refactoringouttheskew,you'llwantlotsofsamplestoaverageacross.You'llalsowanttounderstandsomethingaboutjusthowslowtheworstsampleis,howfastthebestsampleis,howfarapartthosebestandworsecaseswere,andsoon.You'llwanttoknownotjustanumberthattellsyouhowfastsomethingran,butalsotohavesomequantifiablemeasureofhowtrustablethatnumberis.

Also,youprobablywanttocombinethesedifferenttechniques(aswellasothers),sothatyougetthebestbalanceofallthepossibleapproaches.

That'sallbareminimumjusttogetstarted.Ifyou'vebeenapproachingperformancebenchmarkingwithanythinglessseriousthanwhatIjustglossedover,well..."youdon'tknow:properbenchmarking."

Anyrelevantandreliablebenchmarkshouldbebasedonstatisticallysoundpractices.Iamnotgoingtowriteachapteronstatisticshere,soI'llhandwavearoundsometerms:standarddeviation,variance,marginoferror.Ifyoudon'tknowwhatthosetermsreallymean--ItookastatsclassbackincollegeandI'mstillalittlefuzzyonthem--youarenotactuallyqualifiedtowriteyourownbenchmarkinglogic.

Luckily,smartfolkslikeJohn-DavidDaltonandMathiasBynensdounderstandtheseconcepts,andwroteastatisticallysoundbenchmarkingtoolcalledBenchmark.js(http://benchmarkjs.com/).SoIcanendthesuspensebysimplysaying:"justusethattool."

Iwon'trepeattheirwholedocumentationforhowBenchmark.jsworks;theyhavefantasticAPIDocs(http://benchmarkjs.com/docs)youshouldread.Alsotherearesomegreat(http://calendar.perfplanet.com/2010/bulletproof-javascript-benchmarks/)writeups(http://monsur.hossa.in/2012/12/11/benchmarkjs.html)onmoreofthedetailsandmethodology.

Butjustforquickillustrationpurposes,here'showyoucoulduseBenchmark.jstorunaquickperformancetest:

functionfoo(){

Repetition

Benchmark.js

Page 128: You Don't Know JS: Async & Performance

//operation(s)totest

}

varbench=newBenchmark(

"footest",//testname

foo,//functiontotest(justcontents)

{

//..//optionalextraoptions(seedocs)

}

);

bench.hz;//numberofoperationspersecond

bench.stats.moe;//marginoferror

bench.stats.variance;//varianceacrosssamples

//..

There'slotsmoretolearnaboutusingBenchmark.jsbesidesthisglanceI'mincludinghere.Butthepointisthatit'shandlingallofthecomplexitiesofsettingupafair,reliable,andvalidperformancebenchmarkforagivenpieceofJavaScriptcode.Ifyou'regoingtotrytotestandbenchmarkyourcode,thislibraryisthefirstplaceyoushouldturn.

We'reshowingheretheusagetotestasingleoperationlikeX,butit'sfairlycommonthatyouwanttocompareXtoY.Thisiseasytodobysimplysettinguptwodifferenttestsina"Suite"(aBenchmark.jsorganizationalfeature).Then,yourunthemhead-to-head,andcomparethestatisticstoconcludewhetherXorYwasfaster.

Benchmark.jscanofcoursebeusedtotestJavaScriptinabrowser(seethe"jsPerf.com"sectionlaterinthischapter),butitcanalsoruninnon-browserenvironments(Node.js,etc.).

Onelargelyuntappedpotentialuse-caseforBenchmark.jsistouseitinyourDevorQAenvironmentstorunautomatedperformanceregressiontestsagainstcriticalpathpartsofyourapplication'sJavaScript.Similartohowyoumightrununittestsuitesbeforedeployment,youcanalsocomparetheperformanceagainstpreviousbenchmarkstomonitorifyouareimprovingordegradingapplicationperformance.

Inthepreviouscodesnippet,weglossedoverthe"extraoptions"{..}object.Buttherearetwooptionsweshoulddiscuss:setupandteardown.

Thesetwooptionsletyoudefinefunctionstobecalledbeforeandafteryourtestcaseruns.

It'sincrediblyimportanttounderstandthatyoursetupandteardowncodedoesnotrunforeachtestiteration.Thebestwaytothinkaboutitisthatthere'sanouterloop(repeatingcycles),andaninnerloop(repeatingtestiterations).setupandteardownarerunatthebeginningandendofeachouterloop(akacycle)iteration,butnotinsidetheinnerloop.

Whydoesthismatter?Let'simagineyouhaveatestcasethatlookslikethis:

a=a+"w";

b=a.charAt(1);

Then,yousetupyourtestsetupasfollows:

vara="x";

Yourtemptationisprobablytobelievethataisstartingoutas"x"foreachtestiteration.

Butit'snot!It'sstartingaat"x"foreachtestcycle,andthenyourrepeated+"w"concatenationswillbemakingalargerandlargeravalue,eventhoughyou'reonlyeveraccessingthecharacter"w"atthe1position.

WherethismostcommonlybitesyouiswhenyoumakesideeffectchangestosomethingliketheDOM,likeappendingachildelement.Youmaythinkyourparentelementissetasemptyeachtime,butit'sactuallygettinglotsofelementsadded,

Setup/Teardown

Page 129: You Don't Know JS: Async & Performance

andthatcansignificantlyswaytheresultsofyourtests.

Don'tforgettocheckthecontextofaparticularperformancebenchmark,especiallyacomparisonbetweenXandYtasks.JustbecauseyourtestrevealsthatXisfasterthanYdoesn'tmeanthattheconclusion"XisfasterthanY"isactuallyrelevant.

Forexample,let'ssayaperformancetestrevealsthatXruns10,000,000operationspersecond,andYrunsat8,000,000operationspersecond.YoucouldclaimthatYis20%slowerthanX,andyou'dbemathematicallycorrect,butyourassertiondoesn'tholdasmuchwaterasyou'dthink.

Let'sthinkabouttheresultsmorecritically:10,000,000operationspersecondis10,000operationspermillisecond,and10operationspermicrosecond.Inotherwords,asingleoperationtakes0.1microseconds,or100nanoseconds.It'shardtofathomjusthowsmall100nsis,butforcomparison,it'softencitedthatthehumaneyeisn'tgenerallycapableofdistinguishinganythinglessthan100ms,whichisonemilliontimesslowerthanthe100nsspeedoftheXoperation.

Evenrecentscientificstudiesshowingthatmaybethebraincanprocessasquickas13ms(about8xfasterthanpreviouslyasserted)wouldmeanthatXisstillrunning125,000timesfasterthanthehumanbraincanperceiveadistinctthinghappening.Xisgoingreally,reallyfast.

Butmoreimportantly,let'stalkaboutthedifferencebetweenXandY,the2,000,000operationsperseconddifference.IfXtakes100ns,andYtakes80ns,thedifferenceis20ns,whichinthebestcaseisstillone650-thousandthoftheintervalthehumanbraincanperceive.

What'smypoint?Noneofthisperformancedifferencematters,atall!

Butwait,whatifthisoperationisgoingtohappenawholebunchoftimesinarow?Thenthedifferencecouldaddup,right?

OK,sowhatwe'reaskingthenis,howlikelyisitthatoperationXisgoingtoberunoverandoveragain,onerightaftertheother,andthatthishastohappen650,000timesjusttogetasliverofahopethehumanbraincouldperceiveit.Morelikely,it'dhavetohappen5,000,000to10,000,000timestogetherinatightlooptoevenapproachrelevance.

Whilethecomputerscientistinyoumightprotestthatthisispossible,theloudervoiceofrealisminyoushouldsanitycheckjusthowlikelyorunlikelythatreallyis.Evenifitisrelevantinrareoccasions,it'sirrelevantinmostsituations.

Thevastmajorityofyourbenchmarkresultsontinyoperations--likethe++xvsx++myth--arejusttotallybogusforsupportingtheconclusionthatXshouldbefavoredoverYonaperformancebasis.

YousimplycannotreliablyextrapolatethatifXwas10microsecondsfasterthanYinyourisolatedtest,thatmeansXisalwaysfasterthanYandshouldalwaysbeused.That'snothowperformanceworks.It'svastlymorecomplicated.

Forexample,let'simagine(purelyhypothetical)thatyoutestsomemicroperformancebehaviorsuchascomparing:

vartwelve="12";

varfoo="foo";

//test1

varX1=parseInt(twelve);

varX2=parseInt(foo);

//test2

varY1=Number(twelve);

varY2=Number(foo);

IfyouunderstandwhatparseInt(..)doescomparedtoNumber(..),youmightintuitthatparseInt(..)potentiallyhas

ContextIsKing

EngineOptimizations

Page 130: You Don't Know JS: Async & Performance

"morework"todo,especiallyinthefoocase.Oryoumightintuitthattheyshouldhavethesameamountofworktodointhefoocase,asbothshouldbeabletostopatthefirstcharacter"f".

Whichintuitioniscorrect?Ihonestlydon'tknow.ButI'llmakethecaseitdoesn'tmatterwhatyourintuitionis.Whatmighttheresultsbewhenyoutestit?Again,I'mmakingupapurehypotheticalhere,Ihaven'tactuallytried,nordoIcare.

Let'spretendthetestcomesbackthatXandYarestatisticallyidentical.Haveyouthenconfirmedyourintuitionaboutthe"f"characterthing?Nope.

It'spossibleinourhypotheticalthattheenginemightrecognizethatthevariablestwelveandfooareonlybeingusedinoneplaceineachtest,andsoitmightdecidetoinlinethosevalues.ThenitmayrealizethatNumber("12")canjustbereplacedby12.AndmaybeitcomestothesameconclusionwithparseInt(..),ormaybenot.

Oranengine'sdead-coderemovalheuristiccouldkickin,anditcouldrealizethatvariablesXandYaren'tbeingused,sodeclaringthemisirrelevant,soitdoesn'tendupdoinganythingatallineithertest.

Andallthat'sjustmadewiththemindsetofassumptionsaboutasingletestrun.Modernenginesarefantasticallymorecomplicatedthanwhatwe'reintuitinghere.Theydoallsortsoftricks,liketracingandtrackinghowapieceofcodebehavesoverashortperiodoftime,orwithaparticularlyconstrainedsetofinputs.

Whatiftheengineoptimizesacertainwaybecauseofthefixedinput,butinyourrealprogramyougivemorevariedinputandtheoptimizationdecisionsshakeoutdifferently(ornotatall!)?Orwhatiftheenginekicksinoptimizationsbecauseitseesthecodebeingruntensofthousandsoftimesbythebenchmarkingutility,butinyourrealprogramitwillonlyrunahundredtimesinnearproximity,andunderthoseconditionstheenginedeterminestheoptimizationsarenotworthit?

Andallthoseoptimizationswejusthypothesizedaboutmighthappeninourconstrainedtestbutmaybetheenginewouldn'tdotheminamorecomplexprogram(forvariousreasons).Oritcouldbereversed--theenginemightnotoptimizesuchtrivialcodebutmaybemoreinclinedtooptimizeitmoreaggressivelywhenthesystemisalreadymoretaxedbyamoresophisticatedprogram.

ThepointI'mtryingtomakeisthatyoureallydon'tknowforsureexactlywhat'sgoingonunderthecovers.Alltheguessesandhypothesisyoucanmusterdon'tamounttohardlyanythingconcreteforreallymakingsuchdecisions.

Doesthatmeanyoucan'treallydoanyusefultesting?Definitelynot!

Whatthisboilsdowntoisthattestingnotrealcodegivesyounotrealresults.Insomuchasispossibleandpractical,youshouldtestactualreal,non-trivialsnippetsofyourcode,andunderasbestofrealconditionsasyoucanactuallyhopeto.Onlythenwilltheresultsyougethaveachancetoapproximatereality.

Microbenchmarkslike++xvsx++aresoincrediblylikelytobebogus,wemightaswelljustflatlyassumethemassuch.

WhileBenchmark.jsisusefulfortestingtheperformanceofyourcodeinwhateverJSenvironmentyou'rerunning,itcannotbestressedenoughthatyouneedtocompiletestresultsfromlotsofdifferentenvironments(desktopbrowsers,mobiledevices,etc.)ifyouwanttohaveanyhopeofreliabletestconclusions.

Forexample,Chromeonahigh-enddesktopmachineisnotlikelytoperformanywherenearthesameasChromemobileonasmartphone.Andasmartphonewithafullbatterychargeisnotlikelytoperformanywherenearthesameasasmartphonewith2%batterylifeleft,whenthedeviceisstartingtopowerdowntheradioandprocessor.

Ifyouwanttomakeassertionslike"XisfasterthanY"inanyreasonablesenseacrossmorethanjustasingleenvironment,you'regoingtoneedtoactuallytestasmanyofthoserealworldenvironmentsaspossible.JustbecauseChromeexecutessomeXoperationfasterthanYdoesn'tmeanthatallbrowsersdo.Andofcourseyoualsoprobablywillwanttocross-referencetheresultsofmultiplebrowsertestrunswiththedemographicsofyourusers.

There'sanawesomewebsiteforthispurposecalledjsPerf(http://jsperf.com).ItusestheBenchmark.jslibrarywetalked

jsPerf.com

Page 131: You Don't Know JS: Async & Performance

aboutearliertorunstatisticallyaccurateandreliabletests,andmakesthetestonanopenlyavailableURLthatyoucanpassaroundtoothers.

Eachtimeatestisrun,theresultsarecollectedandpersistedwiththetest,andthecumulativetestresultsaregraphedonthepageforanyonetosee.

Whencreatingatestonthesite,youstartoutwithtwotestcasestofillin,butyoucanaddasmanyasyouneed.Youalsohavetheabilitytosetupsetupcodethatisrunatthebeginningofeachtestcycleandteardowncoderunattheendofeachcycle.

Note:Atrickfordoingjustonetestcase(ifyou'rebenchmarkingasingleapproachinsteadofahead-to-head)istofillinthesecondtestinputboxeswithplaceholdertextonfirstcreation,theneditthetestandleavethesecondtestblank,whichwilldeleteit.Youcanalwaysaddmoretestcaseslater.

Youcandefinetheinitialpagesetup(importinglibraries,definingutilityhelperfunctions,declaringvariables,etc.).Therearealsooptionsfordefiningsetupandteardownbehaviorifneeded--consultthe"Setup/Teardown"sectionintheBenchmark.jsdiscussionearlier.

jsPerfisafantasticresource,butthere'sanawfullotoftestspublishedthatwhenyouanalyzethemarequiteflawedorbogus,foranyofavarietyofreasonsasoutlinedsofarinthischapter.

Consider:

//Case1

varx=[];

for(vari=0;i<10;i++){

x[i]="x";

}

//Case2

varx=[];

for(vari=0;i<10;i++){

x[x.length]="x";

}

//Case3

varx=[];

for(vari=0;i<10;i++){

x.push("x");

}

Someobservationstoponderaboutthistestscenario:

It'sextremelycommonfordevstoputtheirownloopsintotestcases,andtheyforgetthatBenchmark.jsalreadydoesalltherepetitionyouneed.There'sareallystrongchancethattheforloopsinthesecasesaretotallyunnecessarynoise.Thedeclaringandinitializingofxisincludedineachtestcase,possiblyunnecessarily.Recallfromearlierthatifx=[]wereinthesetupcode,itwouldn'tactuallyberunbeforeeachtestiteration,butinsteadonceatthebeginningofeachcycle.Thatmeansxwouldcontinuegrowingquitelarge,notjustthesize10impliedbytheforloops.

SoistheintenttomakesurethetestsareconstrainedonlytohowtheJSenginebehaveswithverysmallarrays(size10)?Thatcouldbetheintent,butifitis,youhavetoconsiderifthat'snotfocusingfartoomuchonnuancedinternalimplementationdetails.

Ontheotherhand,doestheintentofthetestembracethecontextthatthearrayswillactuallybegrowingquitelarge?IstheJSengines'behaviorwithlargerarraysrelevantandaccuratewhencomparedwiththeintendedrealworldusage?

Istheintenttofindouthowmuchx.lengthorx.push(..)addtotheperformanceoftheoperationtoappendtothe

SanityCheck

Page 132: You Don't Know JS: Async & Performance

xarray?OK,thatmightbeavalidthingtotest.Butthenagain,push(..)isafunctioncall,soofcourseit'sgoingtobeslowerthan[..]access.Arguably,cases1and2arefairerthancase3.

Here'sanotherexamplethatillustratesacommonapples-to-orangesflaw:

//Case1

varx=["John","Albert","Sue","Frank","Bob"];

x.sort();

//Case2

varx=["John","Albert","Sue","Frank","Bob"];

x.sort(functionmySort(a,b){

if(a<b)return-1;

if(a>b)return1;

return0;

});

Here,theobviousintentistofindouthowmuchslowerthecustommySort(..)comparatoristhanthebuilt-indefaultcomparator.ButbyspecifyingthefunctionmySort(..)asinlinefunctionexpression,you'vecreatedanunfair/bogustest.Here,thesecondcaseisnotonlytestingacustomuserJSfunction,butit'salsotestingcreatinganewfunctionexpressionforeachiteration.

Woulditsurpriseyoutofindoutthatifyourunasimilartestbutupdateittoisolateonlyforcreatinganinlinefunctionexpressionversususingapre-declaredfunction,theinlinefunctionexpressioncreationcanbefrom2%to20%slower!?

Unlessyourintentwiththistestistoconsidertheinlinefunctionexpressioncreation"cost,"abetter/fairertestwouldputmySort(..)'sdeclarationinthepagesetup--don'tputitinthetestsetupasthat'sunnecessaryredeclarationforeachcycle--andsimplyreferenceitbynameinthetestcase:x.sort(mySort).

Buildingonthepreviousexample,anotherpitfallisinopaquelyavoidingoradding"extrawork"toonetestcasethatcreatesanapples-to-orangesscenario:

//Case1

varx=[12,-14,0,3,18,0,2.9];

x.sort();

//Case2

varx=[12,-14,0,3,18,0,2.9];

x.sort(functionmySort(a,b){

returna-b;

});

Settingasidethepreviouslymentionedinlinefunctionexpressionpitfall,thesecondcase'smySort(..)worksinthiscasebecauseyouhaveprovideditnumbers,butwouldhaveofcoursefailedwithstrings.Thefirstcasedoesn'tthrowanerror,butitactuallybehavesdifferentlyandhasadifferentoutcome!Itshouldbeobvious,but:adifferentoutcomebetweentwotestcasesalmostcertainlyinvalidatestheentiretest!

Butbeyondthedifferentoutcomes,inthiscase,thebuiltinsort(..)'scomparatorisactuallydoing"extrawork"thatmySort()doesnot,inthatthebuilt-inonecoercesthecomparedvaluestostringsanddoeslexicographiccomparison.Thefirstsnippetresultsin[-14,0,0,12,18,2.9,3]whilethesecondsnippetresults(likelymoreaccuratelybasedonintent)in[-14,0,0,2.9,3,12,18].

Sothattestisunfairbecauseit'snotactuallydoingthesametaskbetweenthecases.Anyresultsyougetarebogus.

Thesesamepitfallscanevenbemuchmoresubtle:

//Case1

varx=false;

vary=x?1:2;

//Case2

varx;

Page 133: You Don't Know JS: Async & Performance

vary=x?1:2;

Here,theintentmightbetotesttheperformanceimpactofthecoerciontoaBooleanthatthe?:operatorwilldoifthexexpressionisnotalreadyaBoolean(seetheTypes&Grammartitleofthisbookseries).So,you'reapparentlyOKwiththefactthatthereisextraworktodothecoercioninthesecondcase.

Thesubtleproblem?You'resettingx'svalueinthefirstcaseandnotsettingitintheother,soyou'reactuallydoingworkinthefirstcasethatyou'renotdoinginthesecond.Toeliminateanypotential(albeitminor)skew,try:

//Case1

varx=false;

vary=x?1:2;

//Case2

varx=undefined;

vary=x?1:2;

Nowthere'sanassignmentinbothcases,sothethingyouwanttotest--thecoercionofxornot--haslikelybeenmoreaccuratelyisolatedandtested.

LetmeseeifIcanarticulatethebiggerpointI'mtryingtomakehere.

Goodtestauthoringrequirescarefulanalyticalthinkingaboutwhatdifferencesexistbetweentwotestcasesandwhetherthedifferencesbetweenthemareintentionalorunintentional.

IntentionaldifferencesareofcoursenormalandOK,butit'stooeasytocreateunintentionaldifferencesthatskewyourresults.Youhavetobereally,reallycarefultoavoidthatskew.Moreover,youmayintendadifferencebutitmaynotbeobvioustootherreadersofyourtestwhatyourintentwas,sotheymaydoubt(ortrust!)yourtestincorrectly.Howdoyoufixthat?

Writebetter,clearertests.Butalso,takethetimetodocument(usingthejsPerf.com"Description"fieldand/orcodecomments)exactlywhattheintentofyourtestis,eventothenuanceddetail.Callouttheintentionaldifferences,whichwillhelpothersandyourfutureselftobetteridentifyunintentionaldifferencesthatcouldbeskewingthetestresults.

Isolatethingswhicharen'trelevanttoyourtestbypre-declaringtheminthepageortestsetupsettingssothey'reoutsidethetimedpartsofthetest.

Insteadoftryingtonarrowinonatinysnippetofyourrealcodeandbenchmarkingjustthatpieceoutofcontext,testsandbenchmarksarebetterwhentheyincludealarger(whilestillrelevant)context.Thosetestsalsotendtorunslower,whichmeansanydifferencesyouspotaremorerelevantincontext.

OK,untilnowwe'vebeendancingaroundvariousmicroperformanceissuesandgenerallylookingdisfavorablyuponobsessingaboutthem.Iwanttotakejustamomenttoaddressthemdirectly.

Thefirstthingyouneedtogetmorecomfortablewithwhenthinkingaboutperformancebenchmarkingyourcodeisthatthecodeyouwriteisnotalwaysthecodetheengineactuallyruns.WebrieflylookedatthattopicbackinChapter1whenwediscussedstatementreorderingbythecompiler,butherewe'regoingtosuggestthecompilercansometimesdecidetorundifferentcodethanyouwrote,notjustindifferentordersbutdifferentinsubstance.

Let'sconsiderthispieceofcode:

varfoo=41;

WritingGoodTests

Microperformance

Page 134: You Don't Know JS: Async & Performance

(function(){

(function(){

(function(baz){

varbar=foo+baz;

//..

})(1);

})();

})();

Youmaythinkaboutthefooreferenceintheinnermostfunctionasneedingtodoathree-levelscopelookup.WecoveredintheScope&Closurestitleofthisbookserieshowlexicalscopeworks,andthefactthatthecompilergenerallycachessuchlookupssothatreferencingfoofromdifferentscopesdoesn'treallypractically"cost"anythingextra.

Butthere'ssomethingdeepertoconsider.Whatifthecompilerrealizesthatfooisn'treferencedanywhereelsebutthatonelocation,anditfurthernoticesthatthevalueneverisanythingexceptthe41asshown?

Isn'titquitepossibleandacceptablethattheJScompilercoulddecidetojustremovethefoovariableentirely,andinlinethevalue,suchasthis:

(function(){

(function(){

(function(baz){

varbar=41+baz;

//..

})(1);

})();

})();

Note:Ofcourse,thecompilercouldprobablyalsodoasimilaranalysisandrewritewiththebazvariablehere,too.

WhenyoubegintothinkaboutyourJScodeasbeingahintorsuggestiontotheengineofwhattodo,ratherthanaliteralrequirement,yourealizethatalotoftheobsessionoverdiscretesyntacticminutiaismostlikelyunfounded.

Anotherexample:

functionfactorial(n){

if(n<2)return1;

returnn*factorial(n-1);

}

factorial(5);//120

Ah,thegoodol'fashioned"factorial"algorithm!YoumightassumethattheJSenginewillrunthatcodemostlyasis.Andtobehonest,itmight--I'mnotreallysure.

Butasananecdote,thesamecodeexpressedinCandcompiledwithadvancedoptimizationswouldresultinthecompilerrealizingthatthecallfactorial(5)canjustbereplacedwiththeconstantvalue120,eliminatingthefunctionandcallentirely!

Moreover,someengineshaveapracticecalled"unrollingrecursion,"whereitcanrealizethattherecursionyou'veexpressedcanactuallybedone"easier"(i.e.,moreoptimally)withaloop.It'spossibletheprecedingcodecouldberewrittenbyaJSenginetorunas:

functionfactorial(n){

if(n<2)return1;

varres=1;

for(vari=n;i>1;i--){

res*=i;

}

returnres;

}

Page 135: You Don't Know JS: Async & Performance

factorial(5);//120

Now,let'simaginethatintheearliersnippetyouhadbeenworriedaboutwhethern*factorial(n-1)orn*=factorial(--n)runsfaster.Maybeyouevendidaperformancebenchmarktotrytofigureoutwhichwasbetter.Butyoumissthefactthatinthebiggercontext,theenginemaynotruneitherlineofcodebecauseitmayunrolltherecursion!

Speakingof--,--nversusn--isoftencitedasoneofthoseplaceswhereyoucanoptimizebychoosingthe--nversion,becausetheoreticallyitrequireslesseffortdownattheassemblylevelofprocessing.

ThatsortofobsessionisbasicallynonsenseinmodernJavaScript.That'sthekindofthingyoushouldbelettingtheenginetakecareof.Youshouldwritethecodethatmakesthemostsense.Comparethesethreeforloops:

//Option1

for(vari=0;i<10;i++){

console.log(i);

}

//Option2

for(vari=0;i<10;++i){

console.log(i);

}

//Option3

for(vari=-1;++i<10;){

console.log(i);

}

Evenifyouhavesometheorywherethesecondorthirdoptionismoreperformantthanthefirstoptionbyatinybit,whichisdubiousatbest,thethirdloopismoreconfusingbecauseyouhavetostartwith-1foritoaccountforthefactthat++ipre-incrementisused.Andthedifferencebetweenthefirstandsecondoptionsisreallyquiteirrelevant.

It'sentirelypossiblethataJSenginemayseeaplacewherei++isusedandrealizethatitcansafelyreplaceitwiththe++iequivalent,whichmeansyourtimespentdecidingwhichonetopickwascompletelywastedandtheoutcomemoot.

Here'sanothercommonexampleofsillymicroperformanceobsession:

varx=[..];

//Option1

for(vari=0;i<x.length;i++){

//..

}

//Option2

for(vari=0,len=x.length;i<len;i++){

//..

}

Thetheoryheregoesthatyoushouldcachethelengthofthexarrayinthevariablelen,becauseostensiblyitdoesn'tchange,toavoidpayingthepriceofx.lengthbeingconsultedforeachiterationoftheloop.

Ifyourunperformancebenchmarksaroundx.lengthusagecomparedtocachingitinalenvariable,you'llfindthatwhilethetheorysoundsnice,inpracticeanymeasureddifferencesarestatisticallycompletelyirrelevant.

Infact,insomeengineslikev8,itcanbeshown(http://mrale.ph/blog/2014/12/24/array-length-caching.html)thatyoucouldmakethingsslightlyworsebypre-cachingthelengthinsteadoflettingtheenginefigureitoutforyou.Don'ttrytooutsmartyourJavaScriptengine,you'llprobablylosewhenitcomestoperformanceoptimizations.

ThedifferentJSenginesinvariousbrowserscanallbe"speccompliant"whilehavingradicallydifferentwaysofhandling

NotAllEnginesAreAlike

Page 136: You Don't Know JS: Async & Performance

code.TheJSspecificationdoesn'trequireanythingperformancerelated--well,exceptES6's"TailCallOptimization"coveredlaterinthischapter.

Theenginesarefreetodecidethatoneoperationwillreceiveitsattentiontooptimize,perhapstradingoffforlesserperformanceonanotheroperation.Itcanbeverytenuoustofindanapproachforanoperationthatalwaysrunsfasterinallbrowsers.

There'samovementamongsomeintheJSdevcommunity,especiallythosewhoworkwithNode.js,toanalyzethespecificinternalimplementationdetailsofthev8JavaScriptengineandmakedecisionsaboutwritingJScodethatistailoredtotakebestadvantageofhowv8works.Youcanactuallyachieveasurprisinglyhighdegreeofperformanceoptimizationwithsuchendeavors,sothepayofffortheeffortcanbequitehigh.

Somecommonlycitedexamples(https://github.com/petkaantonov/bluebird/wiki/Optimization-killers)forv8:

Don'tpasstheargumentsvariablefromonefunctiontoanyotherfunction,assuch"leakage"slowsdownthefunctionimplementation.Isolateatry..catchinitsownfunction.Browsersstrugglewithoptimizinganyfunctionwithatry..catchinit,somovingthatconstructtoitsownfunctionmeansyoucontainthede-optimizationharmwhilelettingthesurroundingcodebeoptimizable.

Butratherthanfocusonthosetipsspecifically,let'ssanitycheckthev8-onlyoptimizationapproachinageneralsense.

AreyougenuinelywritingcodethatonlyneedstoruninoneJSengine?EvenifyourcodeisentirelyintendedforNode.jsrightnow,istheassumptionthatv8willalwaysbetheusedJSenginereliable?Isitpossiblethatsomedayafewyearsfromnow,there'sanotherserver-sideJSplatformbesidesNode.jsthatyouchoosetorunyourcodeon?Whatifwhatyouoptimizedforbeforeisnowamuchslowerwayofdoingthatoperationonthenewengine?

Orwhatifyourcodealwaysstaysrunningonv8fromhereonout,butv8decidesatsomepointtochangethewaysomesetofoperationsworkssuchthatwhatusedtobefastisnowslow,andviceversa?

Thesescenariosaren'tjusttheoretical,either.Itusedtobethatitwasfastertoputmultiplestringvaluesintoanarrayandthencalljoin("")onthearraytoconcatenatethevaluesthantojustuse+concatenationdirectlywiththevalues.Thehistoricalreasonforthisisnuanced,butithastodowithinternalimplementationdetailsabouthowstringvalueswerestoredandmanagedinmemory.

Asaresult,"bestpractice"adviceatthetimedisseminatedacrosstheindustrysuggestingdevelopersalwaysusethearrayjoin(..)approach.Andmanyfollowed.

Except,somewherealongtheway,theJSengineschangedapproachesforinternallymanagingstrings,andspecificallyputinoptimizationsfor+concatenation.Theydidn'tslowdownjoin(..)perse,buttheyputmoreeffortintohelping+usage,asitwasstillquiteabitmorewidespread.

Note:Thepracticeofstandardizingoroptimizingsomeparticularapproachbasedmostlyonitsexistingwidespreadusageisoftencalled(metaphorically)"pavingthecowpath."

Oncethatnewapproachtohandlingstringsandconcatenationtookhold,unfortunatelyallthecodeoutinthewildthatwasusingarrayjoin(..)toconcatenatestringswasthensub-optimal.

Anotherexample:atonetime,theOperabrowserdifferedfromotherbrowsersinhowithandledtheboxing/unboxingofprimitivewrapperobjects(seetheTypes&Grammartitleofthisbookseries).Assuch,theiradvicetodeveloperswastouseaStringobjectinsteadoftheprimitivestringvalueifpropertieslikelengthormethodslikecharAt(..)neededtobeaccessed.ThisadvicemayhavebeencorrectforOperaatthetime,butitwasliterallycompletelyoppositeforothermajorcontemporarybrowsers,astheyhadoptimizationsspecificallyforthestringprimitivesandnottheirobjectwrappercounterparts.

Ithinkthesevariousgotchasareatleastpossible,ifnotlikely,forcodeeventoday.SoI'mverycautiousaboutmakingwiderangingperformanceoptimizationsinmyJScodebasedpurelyonengineimplementationdetails,especiallyifthosedetailsareonlytrueofasingleengine.

Page 137: You Don't Know JS: Async & Performance

Thereverseisalsosomethingtobewaryof:youshouldn'tnecessarilychangeapieceofcodetoworkaroundoneengine'sdifficultywithrunningapieceofcodeinanacceptablyperformantway.

Historically,IEhasbeenthebruntofmanysuchfrustrations,giventhattherehavebeenplentyofscenariosinolderIEversionswhereitstruggledwithsomeperformanceaspectthatothermajorbrowsersofthetimeseemednottohavemuchtroublewith.ThestringconcatenationdiscussionwejusthadwasactuallyarealconcernbackintheIE6andIE7days,whereitwaspossibletogetbetterperformanceoutofjoin(..)than+.

Butit'stroublesometosuggestthatjustonebrowser'stroublewithperformanceisjustifcationforusingacodeapproachthatquitepossiblycouldbesub-optimialinallotherbrowsers.Evenifthebrowserinquestionhasalargemarketshareforyoursite'saudience,itmaybemorepracticaltowritethepropercodeandrelyonthebrowsertoupdateitselfwithbetteroptimizationseventually.

"Thereisnothingmorepermanentthanatemporaryhack."Chancesare,thecodeyouwritenowtoworkaroundsomeperformancebugwillprobablyoutlivetheperformancebuginthebrowseritself.

Inthedayswhenabrowseronlyupdatedonceeveryfiveyears,thatwasatoughercalltomake.Butasitstandsnow,browsersacrosstheboardareupdatingatamuchmorerapidinterval(thoughobviouslythemobileworldstilllags),andthey'reallcompetingtooptimizewebfeaturesbetterandbetter.

Ifyourunacrossacasewhereabrowserdoeshaveaperformancewartthatothersdon'tsufferfrom,makesuretoreportittothemthroughwhatevermeansyouhaveavailable.Mostbrowsershaveopenpublicbugtrackerssuitableforthispurpose.

Tip:I'donlysuggestworkingaroundaperformanceissueinabrowserifitwasareallydrasticshow-stopper,notjustanannoyanceorfrustration.AndI'dbeverycarefultocheckthattheperformancehackdidn'thavenoticeablenegativesideeffectsinanotherbrowser.

Insteadofworryingaboutallthesemicroperformancenuances,weshouldinsteadbelookingatbig-picturetypesofoptimizations.

Howdoyouknowwhat'sbigpictureornot?Youhavetofirstunderstandifyourcodeisrunningonacriticalpathornot.Ifit'snotonthecriticalpath,chancesareyouroptimizationsarenotworthmuch.

Everheardtheadmonition,"that'sprematureoptimization!"?ItcomesfromafamousquotefromDonaldKnuth:"prematureoptimizationistherootofallevil.".Manydeveloperscitethisquotetosuggestthatmostoptimizationsare"premature"andarethusawasteofeffort.Thetruthis,asusual,morenuanced.

HereisKnuth'squote,incontext:

Programmerswasteenormousamountsoftimethinkingabout,orworryingabout,thespeedofnoncriticalpartsoftheirprograms,andtheseattemptsatefficiencyactuallyhaveastrongnegativeimpactwhendebuggingandmaintenanceareconsidered.Weshouldforgetaboutsmallefficiencies,sayabout97%ofthetime:prematureoptimizationistherootofallevil.Yetweshouldnotpassupouropportunitiesinthatcritical3%.[emphasisadded]

(http://web.archive.org/web/20130731202547/http://pplab.snu.ac.kr/courses/adv_pl05/papers/p261-knuth.pdf,ComputingSurveys,Vol6,No4,December1974)

Ibelieveit'safairparaphrasingtosaythatKnuthmeant:"non-criticalpathoptimizationistherootofallevil."Sothekeyistofigureoutifyourcodeisonthecriticalpath--youshouldoptimizeit!--ornot.

I'devengosofarastosaythis:noamountoftimespentoptimizingcriticalpathsiswasted,nomatterhowlittleissaved;butnoamountofoptimizationonnoncriticalpathsisjustified,nomatterhowmuchissaved.

Ifyourcodeisonthecriticalpath,suchasa"hot"pieceofcodethat'sgoingtoberunoverandoveragain,orinUXcriticalplaceswhereuserswillnotice,likeananimationlooporCSSstyleupdates,thenyoushouldsparenoeffortintryingto

BigPicture

Page 138: You Don't Know JS: Async & Performance

employrelevant,measurablysignificantoptimizations.

Forexample,consideracriticalpathanimationloopthatneedstocoerceastringvaluetoanumber.Thereareofcoursemultiplewaystodothat(seetheTypes&Grammartitleofthisbookseries),butwhichoneifanyisthefastest?

varx="42";//neednumber`42`

//Option1:letimplicitcoercionautomaticallyhappen

vary=x/2;

//Option2:use`parseInt(..)`

vary=parseInt(x,0)/2;

//Option3:use`Number(..)`

vary=Number(x)/2;

//Option4:use`+`unaryoperator

vary=+x/2;

//Option5:use`|`unaryoperator

vary=(x|0)/2;

Note:Iwillleaveitasanexercisetothereadertosetupatestifyou'reinterestedinexaminingtheminutedifferencesinperformanceamongtheseoptions.

Whenconsideringthesedifferentoptions,astheysay,"Oneofthesethingsisnotliketheothers."parseInt(..)doesthejob,butitalsodoesalotmore--itparsesthestringratherthanjustcoercing.Youcanprobablyguess,correctly,thatparseInt(..)isasloweroption,andyoushouldprobablyavoidit.

Ofcourse,ifxcaneverbeavaluethatneedsparsing,suchas"42px"(likefromaCSSstylelookup),thenparseInt(..)reallyistheonlysuitableoption!

Number(..)isalsoafunctioncall.Fromabehavioralperspective,it'sidenticaltothe+unaryoperatoroption,butitmayinfactbealittleslower,requiringmoremachinerytoexecutethefunction.Ofcourse,it'salsopossiblethattheJSenginerecognizesthisbehavioralsymmetryandjusthandlestheinliningofNumber(..)'sbehavior(aka+x)foryou!

Butremember,obsessingabout+xversusx|0isinmostcaseslikelyawasteofeffort.Thisisamicroperformanceissue,andonethatyoushouldn'tletdictate/degradethereadabilityofyourprogram.

Whileperformanceisveryimportantincriticalpathsofyourprogram,it'snottheonlyfactor.Amongseveraloptionsthatareroughlysimilarinperformance,readabilityshouldbeanotherimportantconcern.

Aswebrieflymentionedearlier,ES6includesaspecificrequirementthatventuresintotheworldofperformance.It'srelatedtoaspecificformofoptimizationthatcanoccurwithfunctioncalls:tailcalloptimization.

Briefly,a"tailcall"isafunctioncallthatappearsatthe"tail"ofanotherfunction,suchthatafterthecallfinishes,there'snothinglefttodo(exceptperhapsreturnitsresultvalue).

Forexample,here'sanon-recursivesetupwithtailcalls:

functionfoo(x){

returnx;

}

functionbar(y){

returnfoo(y+1);//tailcall

}

functionbaz(){

return1+bar(40);//nottailcall

}

TailCallOptimization(TCO)

Page 139: You Don't Know JS: Async & Performance

baz();//42

foo(y+1)isatailcallinbar(..)becauseafterfoo(..)finishes,bar(..)isalsofinishedexceptinthiscasereturningtheresultofthefoo(..)call.However,bar(40)isnotatailcallbecauseafteritcompletes,itsresultvaluemustbeaddedto1beforebaz()canreturnit.

Withoutgettingintotoomuchnitty-grittydetail,callinganewfunctionrequiresanextraamountofreservedmemorytomanagethecallstack,calleda"stackframe."Sotheprecedingsnippetwouldgenerallyrequireastackframeforeachofbaz(),bar(..),andfoo(..)allatthesametime.

However,ifaTCO-capableenginecanrealizethatthefoo(y+1)callisintailpositionmeaningbar(..)isbasicallycomplete,thenwhencallingfoo(..),itdoesn'tneedtocreateanewstackframe,butcaninsteadreusetheexistingstackframefrombar(..).That'snotonlyfaster,butitalsouseslessmemory.

Thatsortofoptimizationisn'tabigdealinasimplesnippet,butitbecomesamuchbiggerdealwhendealingwithrecursion,especiallyiftherecursioncouldhaveresultedinhundredsorthousandsofstackframes.WithTCOtheenginecanperformallthosecallswithasinglestackframe!

RecursionisahairytopicinJSbecausewithoutTCO,engineshavehadtoimplementarbitrary(anddifferent!)limitstohowdeeptheywilllettherecursionstackgetbeforetheystopit,topreventrunningoutofmemory.WithTCO,recursivefunctionswithtailpositioncallscanessentiallyrununbounded,becausethere'sneveranyextrausageofmemory!

Considerthatrecursivefactorial(..)frombefore,butrewrittentomakeitTCOfriendly:

functionfactorial(n){

functionfact(n,res){

if(n<2)returnres;

returnfact(n-1,n*res);

}

returnfact(n,1);

}

factorial(5);//120

Thisversionoffactorial(..)isstillrecursive,butit'salsooptimizablewithTCO,becausebothinnerfact(..)callsareintailposition.

Note:It'simportanttonotethatTCOonlyappliesifthere'sactuallyatailcall.Ifyouwriterecursivefunctionswithouttailcalls,theperformancewillstillfallbacktonormalstackframeallocation,andtheengines'limitsonsuchrecursivecallstackswillstillapply.Manyrecursivefunctionscanberewrittenaswejustshowedwithfactorial(..),butittakescarefulattentiontodetail.

OnereasonthatES6requiresenginestoimplementTCOratherthanleavingituptotheirdiscretionisbecausethelackofTCOactuallytendstoreducethechancesthatcertainalgorithmswillbeimplementedinJSusingrecursion,forfearofthecallstacklimits.

IfthelackofTCOintheenginewouldjustgracefullydegradetoslowerperformanceinallcases,itwouldn'tprobablyhavebeensomethingthatES6neededtorequire.ButbecausethelackofTCOcanactuallymakecertainprogramsimpractical,it'smoreanimportantfeatureofthelanguagethanjustahiddenimplementationdetail.

ES6guaranteesthatfromnowon,JSdeveloperswillbeabletorelyonthisoptimizationacrossallES6+compliantbrowsers.That'sawinforJSperformance!

Effectivelybenchmarkingperformanceofapieceofcode,especiallytocompareittoanotheroptionforthatsamecodeto

Review

Page 140: You Don't Know JS: Async & Performance

seewhichapproachisfaster,requirescarefulattentiontodetail.

Ratherthanrollingyourownstatisticallyvalidbenchmarkinglogic,justusetheBenchmark.jslibrary,whichdoesthatforyou.Butbecarefulabouthowyouauthortests,becauseit'sfartooeasytoconstructatestthatseemsvalidbutthat'sactuallyflawed--eventinydifferencescanskewtheresultstobecompletelyunreliable.

It'simportanttogetasmanytestresultsfromasmanydifferentenvironmentsaspossibletoeliminatehardware/devicebias.jsPerf.comisafantasticwebsiteforcrowdsourcingperformancebenchmarktestruns.

Manycommonperformancetestsunfortunatelyobsessaboutirrelevantmicroperformancedetailslikex++versus++x.Writinggoodtestsmeansunderstandinghowtofocusonbigpictureconcerns,likeoptimizingonthecriticalpath,andavoidingfallingintotrapslikedifferentJSengines'implementationdetails.

Tailcalloptimization(TCO)isarequiredoptimizationasofES6thatwillmakesomerecursivepatternspracticalinJSwheretheywouldhavebeenimpossibleotherwise.TCOallowsafunctioncallinthetailpositionofanotherfunctiontoexecutewithoutneedinganyextraresources,whichmeanstheenginenolongerneedstoplacearbitraryrestrictionsoncallstackdepthforrecursivealgorithms.

Page 141: You Don't Know JS: Async & Performance

Chapters1and2wentintoquiteabitofdetailabouttypicalasynchronousprogrammingpatternsandhowthey'recommonlysolvedwithcallbacks.Butwealsosawwhycallbacksarefatallylimitedincapability,whichledustoChapters3and4,withPromisesandgeneratorsofferingamuchmoresolid,trustable,andreason-ablebasetobuildyourasynchronyon.

Ireferencedmyownasynchronouslibraryasynquence(http://github.com/getify/asynquence)--"async"+"sequence"="asynquence"--severaltimesinthisbook,andIwanttonowbrieflyexplainhowitworksandwhyitsuniquedesignisimportantandhelpful.

Inthenextappendix,we'llexploresomeadvancedasyncpatterns,butyou'llprobablywantalibrarytomakethosepalatableenoughtobeuseful.We'lluseasynquencetoexpressthosepatterns,soyou'llwanttospendalittletimeheregettingtoknowthelibraryfirst.

asynquenceisobviouslynottheonlyoptionforgoodasynccoding;certainlytherearemanygreatlibrariesinthisspace.Butasynquenceprovidesauniqueperspectivebycombiningthebestofallthesepatternsintoasinglelibrary,andmoreoverisbuiltonasinglebasicabstraction:the(async)sequence.

MypremiseisthatsophisticatedJSprogramsoftenneedbitsandpiecesofvariousdifferentasynchronouspatternswoventogether,andthisisusuallyleftentirelyuptoeachdevelopertofigureout.Insteadofhavingtobringintwoormoredifferentasynclibrariesthatfocusondifferentaspectsofasynchrony,asynquenceunifiesthemintovariatedsequencesteps,withjustonecorelibrarytolearnanddeploy.

IbelievethevalueisstrongenoughwithasynquencetomakeasyncflowcontrolprogrammingwithPromise-stylesemanticssupereasytoaccomplish,sothat'swhywe'llexclusivelyfocusonthatlibraryhere.

Tobegin,I'llexplainthedesignprinciplesbehindasynquence,andthenwe'llillustratehowitsAPIworkswithcodeexamples.

Understandingasynquencebeginswithunderstandingafundamentalabstraction:anyseriesofstepsforatask,whethertheyseparatelyaresynchronousorasynchronous,canbecollectivelythoughtofasa"sequence".Inotherwords,asequenceisacontainerthatrepresentsatask,andiscomprisedofindividual(potentiallyasync)stepstocompletethattask.

EachstepinthesequenceiscontrolledunderthecoversbyaPromise(seeChapter3).Thatis,everystepyouaddtoasequenceimplicitlycreatesaPromisethatiswiredtothepreviousendofthesequence.BecauseofthesemanticsofPromises,everysinglestepadvancementinasequenceisasynchronous,evenifyousynchronouslycompletethestep.

Moreover,asequencewillalwaysproceedlinearlyfromsteptostep,meaningthatstep2alwayscomesafterstep1finishes,andsoon.

Ofcourse,anewsequencecanbeforkedoffanexistingsequence,meaningtheforkonlyoccursoncethemainsequencereachesthatpointintheflow.Sequencescanalsobecombinedinvariousways,includinghavingonesequencesubsumedbyanothersequenceataparticularpointintheflow.

AsequenceiskindoflikeaPromisechain.However,withPromisechains,thereisno"handle"tograbthatreferencestheentirechain.WhicheverPromiseyouhaveareferencetoonlyrepresentsthecurrentstepinthechainplusanyotherstepshangingoffit.Essentially,youcannotholdareferencetoaPromisechainunlessyouholdareferencetothefirstPromiseinthechain.

YouDon'tKnowJS:Async&Performance

AppendixA:asynquenceLibrary

Sequences,AbstractionDesign

Page 142: You Don't Know JS: Async & Performance

Therearemanycaseswhereitturnsouttobequiteusefultohaveahandlethatreferencestheentiresequencecollectively.Themostimportantofthosecasesiswithsequenceabort/cancel.AswecoveredextensivelyinChapter3,Promisesthemselvesshouldneverbeabletobecanceled,asthisviolatesafundamentaldesignimperative:externalimmutability.

Butsequenceshavenosuchimmutabilitydesignprinciple,mostlybecausesequencesarenotpassedaroundasfuture-valuecontainersthatneedimmutablevaluesemantics.Sosequencesaretheproperlevelofabstractiontohandleabort/cancelbehavior.asynquencesequencescanbeabort()edatanytime,andthesequencewillstopatthatpointandnotgoforanyreason.

There'splentymorereasonstopreferasequenceabstractionontopofPromisechains,forflowcontrolpurposes.

First,Promisechainingisarathermanualprocess--onethatcangetprettytediousonceyoustartcreatingandchainingPromisesacrossawideswathofyourprograms--andthistediumcanactcounterproductivelytodissuadethedeveloperfromusingPromisesinplaceswheretheyarequiteappropriate.

Abstractionsaremeanttoreduceboilerplateandtedium,sothesequenceabstractionisagoodsolutiontothisproblem.WithPromises,yourfocusisontheindividualstep,andthere'slittleassumptionthatyouwillkeepthechaingoing.Withsequences,theoppositeapproachistaken,assumingthesequencewillkeephavingmorestepsaddedindefinitely.

Thisabstractioncomplexityreductionisespeciallypowerfulwhenyoustartthinkingabouthigher-orderPromisepatterns(beyondrace([..])andall([..]).

Forexample,inthemiddleofasequence,youmaywanttoexpressastepthatisconceptuallylikeatry..catchinthatthestepwillalwaysresultinsuccess,eithertheintendedmainsuccessresolutionorapositivenonerrorsignalforthecaughterror.Or,youmightwanttoexpressastepthatislikearetry/untilloop,whereitkeepstryingthesamestepoverandoveruntilsuccessoccurs.

ThesesortsofabstractionsarequitenontrivialtoexpressusingonlyPromiseprimitives,anddoingsointhemiddleofanexistingPromisechainisnotpretty.Butifyouabstractyourthinkingtoasequence,andconsiderastepasawrapperaroundaPromise,thatstepwrappercanhidesuchdetails,freeingyoutothinkabouttheflowcontrolinthemostsensiblewaywithoutbeingbotheredbythedetails.

Second,andperhapsmoreimportantly,thinkingofasyncflowcontrolintermsofstepsinasequenceallowsyoutoabstractoutthedetailsofwhattypesofasynchronicityareinvolvedwitheachindividualstep.Underthecovers,aPromisewillalwayscontrolthestep,butabovethecovers,thatstepcanlookeitherlikeacontinuationcallback(thesimpledefault),orlikearealPromise,orasarun-to-completiongenerator,or...Hopefully,yougetthepicture.

Third,sequencescanmoreeasilybetwistedtoadapttodifferentmodesofthinking,suchasevent-,stream-,orreactive-basedcoding.asynquenceprovidesapatternIcall"reactivesequences"(whichwe'llcoverlater)asavariationonthe"reactiveobservable"ideasinRxJS("ReactiveExtensions"),thatletsarepeatableeventfireoffanewsequenceinstanceeachtime.Promisesareone-shot-only,soit'squiteawkwardtoexpressrepetitiousasynchronywithPromisesalone.

Anotheralternatemodeofthinkinginvertstheresolution/controlcapabilityinapatternIcall"iterablesequences".Insteadofeachindividualstepinternallycontrollingitsowncompletion(andthusadvancementofthesequence),thesequenceisinvertedsotheadvancementcontrolisthroughanexternaliterator,andeachstepintheiterablesequencejustrespondstothenext(..)iteratorcontrol.

We'llexploreallofthesedifferentvariationsaswegothroughouttherestofthisappendix,sodon'tworryifweranoverthosebitsfartooquicklyjustnow.

ThetakeawayisthatsequencesareamorepowerfulandsensibleabstractionforcomplexasynchronythanjustPromises(Promisechains)orjustgenerators,andasynquenceisdesignedtoexpressthatabstractionwithjusttherightlevelofsugartomakeasyncprogrammingmoreunderstandableandmoreenjoyable.

asynquenceAPI

Page 143: You Don't Know JS: Async & Performance

Tostartoff,thewayyoucreateasequence(anasynquenceinstance)iswiththeASQ(..)function.AnASQ()callwithnoparameterscreatesanemptyinitialsequence,whereaspassingoneormorevaluesorfunctionstoASQ(..)setsupthesequencewitheachargumentrepresentingtheinitialstepsofthesequence.

Note:Forthepurposesofallcodeexampleshere,Iwillusetheasynquencetop-levelidentifieringlobalbrowserusage:ASQ.Ifyouincludeanduseasynquencethroughamodulesystem(browserorserver),youofcoursecandefinewhicheversymbolyouprefer,andasynquencewon'tcare!

ManyoftheAPImethodsdiscussedherearebuiltintothecoreofasynquence,butothersareprovidedthroughincludingtheoptional"contrib"plug-inspackage.Seethedocumentationforasynquenceforwhetheramethodisbuiltinordefinedviaplug-in:http://github.com/getify/asynquence

Ifafunctionrepresentsanormalstepinthesequence,thatfunctionisinvokedwiththefirstparameterbeingthecontinuationcallback,andanysubsequentparametersbeinganymessagespassedonfromthepreviousstep.Thestepwillnotcompleteuntilthecontinuationcallbackiscalled.Onceit'scalled,anyargumentsyoupasstoitwillbesentalongasmessagestothenextstepinthesequence.

Toaddanadditionalnormalsteptothesequence,callthen(..)(whichhasessentiallytheexactsamesemanticsastheASQ(..)call):

ASQ(

//step1

function(done){

setTimeout(function(){

done("Hello");

},100);

},

//step2

function(done,greeting){

setTimeout(function(){

done(greeting+"World");

},100);

}

)

//step3

.then(function(done,msg){

setTimeout(function(){

done(msg.toUpperCase());

},100);

})

//step4

.then(function(done,msg){

console.log(msg);//HELLOWORLD

});

Note:Thoughthenamethen(..)isidenticaltothenativePromisesAPI,thisthen(..)isdifferent.Youcanpassasfeworasmanyfunctionsorvaluestothen(..)asyou'dlike,andeachistakenasaseparatestep.There'snotwo-callbackfulfilled/rejectedsemanticsinvolved.

UnlikewithPromises,wheretochainonePromisetothenextyouhavetocreateandreturnthatPromisefromathen(..)fulfillmenthandler,withasynquence,allyouneedtodoiscallthecontinuationcallback--Ialwayscallitdone()butyoucannameitwhateversuitsyou--andoptionallypassitcompletionmessagesasarguments.

Eachstepdefinedbythen(..)isassumedtobeasynchronous.Ifyouhaveastepthat'ssynchronous,youcaneitherjustcalldone(..)rightaway,oryoucanusethesimplerval(..)stephelper:

//step1(sync)

ASQ(function(done){

done("Hello");//manuallysynchronous

})

//step2(sync)

.val(function(greeting){

Steps

Page 144: You Don't Know JS: Async & Performance

returngreeting+"World";

})

//step3(async)

.then(function(done,msg){

setTimeout(function(){

done(msg.toUpperCase());

},100);

})

//step4(sync)

.val(function(msg){

console.log(msg);

});

Asyoucansee,val(..)-invokedstepsdon'treceiveacontinuationcallback,asthatpartisassumedforyou--andtheparameterlistislessclutteredasaresult!Tosendamessagealongtothenextstep,yousimplyusereturn.

Thinkofval(..)asrepresentingasynchronous"value-only"step,whichisusefulforsynchronousvalueoperations,logging,andthelike.

OneimportantdifferencewithasynquencecomparedtoPromisesiswitherrorhandling.

WithPromises,eachindividualPromise(step)inachaincanhaveitsownindependenterror,andeachsubsequentstephastheabilitytohandletheerrorornot.Themainreasonforthissemanticcomes(again)fromthefocusonindividualPromisesratherthanonthechain(sequence)asawhole.

Ibelievethatmostofthetime,anerrorinonepartofasequenceisgenerallynotrecoverable,sothesubsequentstepsinthesequencearemootandshouldbeskipped.So,bydefault,anerroratanystepofasequencethrowstheentiresequenceintoerrormode,andtherestofthenormalstepsareignored.

Ifyoudoneedtohaveastepwhereitserrorisrecoverable,thereareseveraldifferentAPImethodsthatcanaccomodate,suchastry(..)--previouslymentionedasakindoftry..catchstep--oruntil(..)--aretryloopthatkeepsattemptingthestepuntilitsucceedsoryoumanuallybreak()theloop.asynquenceevenhaspThen(..)andpCatch(..)methods,whichworkidenticallytohownormalPromisethen(..)andcatch(..)work(seeChapter3),soyoucandolocalizedmid-sequenceerrorhandlingifyousochoose.

Thepointis,youhavebothoptions,butthemorecommononeinmyexperienceisthedefault.WithPromises,togetachainofstepstoignoreallstepsonceanerroroccurs,youhavetotakecarenottoregisterarejectionhandleratanystep;otherwise,thaterrorgetsswallowedashandled,andthesequencemaycontinue(perhapsunexpectedly).Thiskindofdesiredbehaviorisabitawkwardtoproperlyandreliablyhandle.

Toregisterasequenceerrornotificationhandler,asynquenceprovidesanor(..)sequencemethod,whichalsohasanaliasofonerror(..).Youcancallthismethodanywhereinthesequence,andyoucanregisterasmanyhandlersasyou'dlike.Thatmakesiteasyformultipledifferentconsumerstolisteninonasequencetoknowifitfailedornot;it'skindoflikeanerroreventhandlerinthatrespect.

JustlikewithPromises,allJSexceptionsbecomesequenceerrors,oryoucanprogrammaticallysignalasequenceerror:

varsq=ASQ(function(done){

setTimeout(function(){

//signalanerrorforthesequence

done.fail("Oops");

},100);

})

.then(function(done){

//willnevergethere

})

.or(function(err){

console.log(err);//Oops

})

.then(function(done){

//won'tgethereeither

});

Errors

Page 145: You Don't Know JS: Async & Performance

//later

sq.or(function(err){

console.log(err);//Oops

});

AnotherreallyimportantdifferencewitherrorhandlinginasynquencecomparedtonativePromisesisthedefaultbehaviorof"unhandledexceptions".AswediscussedatlengthinChapter3,arejectedPromisewithoutaregisteredrejectionhandlerwilljustsilentlyhold(akaswallow)theerror;youhavetoremembertoalwaysendachainwithafinalcatch(..).

Inasynquence,theassumptionisreversed.

Ifanerroroccursonasequence,anditatthatmomenthasnoerrorhandlersregistered,theerrorisreportedtotheconsole.Inotherwords,unhandledrejectionsarebydefaultalwaysreportedsoasnottobeswallowedandmissed.

Assoonasyouregisteranerrorhandleragainstasequence,itoptsthatsequenceoutofsuchreporting,topreventduplicatenoise.

Theremay,infact,becaseswhereyouwanttocreateasequencethatmaygointotheerrorstatebeforeyouhaveachancetoregisterthehandler.Thisisn'tcommon,butitcanhappenfromtimetotime.

Inthosecases,youcanalsooptasequenceinstanceoutoferrorreportingbycallingdefer()onthesequence.Youshouldonlyoptoutoferrorreportingifyouaresurethatyou'regoingtoeventuallyhandlesucherrors:

varsq1=ASQ(function(done){

doesnt.Exist();//willthrowexceptiontoconsole

});

varsq2=ASQ(function(done){

doesnt.Exist();//willthrowonlyasequenceerror

})

//opt-outoferrorreporting

.defer();

setTimeout(function(){

sq1.or(function(err){

console.log(err);//ReferenceError

});

sq2.or(function(err){

console.log(err);//ReferenceError

});

},100);

//ReferenceError(fromsq1)

ThisisbettererrorhandlingbehaviorthanPromisesthemselveshave,becauseit'sthePitofSuccess,notthePitofFailure(seeChapter3).

Note:Ifasequenceispipedinto(akasubsumedby)anothersequence--see"CombiningSequences"foracompletedescription--thenthesourcesequenceisoptedoutoferrorreporting,butnowthetargetsequence'serrorreportingorlackthereofmustbeconsidered.

Notallstepsinyoursequenceswillhavejustasingle(async)tasktoperform;somewillneedtoperformmultiplesteps"inparallel"(concurrently).Astepinasequenceinwhichmultiplesubstepsareprocessingconcurrentlyiscalledagate(..)--there'sanall(..)aliasifyouprefer--andisdirectlysymmetrictonativePromise.all([..]).

Ifallthestepsinthegate(..)completesuccessfully,allsuccessmessageswillbepassedtothenextsequencestep.Ifanyofthemgenerateerrors,thewholesequenceimmediatelygoesintoanerrorstate.

Consider:

ParallelSteps

Page 146: You Don't Know JS: Async & Performance

ASQ(function(done){

setTimeout(done,100);

})

.gate(

function(done){

setTimeout(function(){

done("Hello");

},100);

},

function(done){

setTimeout(function(){

done("World","!");

},100);

}

)

.val(function(msg1,msg2){

console.log(msg1);//Hello

console.log(msg2);//["World","!"]

});

Forillustration,let'scomparethatexampletonativePromises:

newPromise(function(resolve,reject){

setTimeout(resolve,100);

})

.then(function(){

returnPromise.all([

newPromise(function(resolve,reject){

setTimeout(function(){

resolve("Hello");

},100);

}),

newPromise(function(resolve,reject){

setTimeout(function(){

//note:weneeda[]arrayhere

resolve(["World","!"]);

},100);

})

]);

})

.then(function(msgs){

console.log(msgs[0]);//Hello

console.log(msgs[1]);//["World","!"]

});

Yuck.Promisesrequirealotmoreboilerplateoverheadtoexpressthesameasynchronousflowcontrol.That'sagreatillustrationofwhytheasynquenceAPIandabstractionmakedealingwithPromisestepsalotnicer.Theimprovementonlygoeshigherthemorecomplexyourasynchronyis.

Thereareseveralvariationsinthecontribplug-insonasynquence'sgate(..)steptypethatcanbequitehelpful:

any(..)islikegate(..),exceptjustonesegmenthastoeventuallysucceedtoproceedonthemainsequence.first(..)islikeany(..),exceptassoonasanysegmentsucceeds,themainsequenceproceeds(ignoringsubsequentresultsfromothersegments).race(..)(symmetricwithPromise.race([..]))islikefirst(..),exceptthemainsequenceproceedsassoonasanysegmentcompletes(eithersuccessorfailure).last(..)islikeany(..),exceptonlythelatestsegmenttocompletesuccessfullysendsitsmessage(s)alongtothemainsequence.none(..)istheinverseofgate(..):themainsequenceproceedsonlyifallthesegmentsfail(withallsegmenterrormessage(s)transposedassuccessmessage(s)andviceversa).

Let'sfirstdefinesomehelperstomakeillustrationcleaner:

functionsuccess1(done){

StepVariations

Page 147: You Don't Know JS: Async & Performance

setTimeout(function(){

done(1);

},100);

}

functionsuccess2(done){

setTimeout(function(){

done(2);

},100);

}

functionfailure3(done){

setTimeout(function(){

done.fail(3);

},100);

}

functionoutput(msg){

console.log(msg);

}

Now,let'sdemonstratethesegate(..)stepvariations:

ASQ().race(

failure3,

success1

)

.or(output);//3

ASQ().any(

success1,

failure3,

success2

)

.val(function(){

varargs=[].slice.call(arguments);

console.log(

args//[1,undefined,2]

);

});

ASQ().first(

failure3,

success1,

success2

)

.val(output);//1

ASQ().last(

failure3,

success1,

success2

)

.val(output);//2

ASQ().none(

failure3

)

.val(output)//3

.none(

failure3

success1

)

.or(output);//1

Anotherstepvariationismap(..),whichletsyouasynchronouslymapelementsofanarraytodifferentvalues,andthestepdoesn'tproceeduntilallthemappingsarecomplete.map(..)isverysimilartogate(..),exceptitgetstheinitialvaluesfromanarrayinsteadoffromseparatelyspecifiedfunctions,andalsobecauseyoudefineasinglefunctioncallbacktooperateoneachvalue:

functiondouble(x,done){

Page 148: You Don't Know JS: Async & Performance

setTimeout(function(){

done(x*2);

},100);

}

ASQ().map([1,2,3],double)

.val(output);//[2,4,6]

Also,map(..)canreceiveeitherofitsparameters(thearrayorthecallback)frommessagespassedfromthepreviousstep:

functionplusOne(x,done){

setTimeout(function(){

done(x+1);

},100);

}

ASQ([1,2,3])

.map(double)//message`[1,2,3]`comesin

.map(plusOne)//message`[2,4,6]`comesin

.val(output);//[3,5,7]

Anothervariationiswaterfall(..),whichiskindoflikeamixturebetweengate(..)'smessagecollectionbehaviorbutthen(..)'ssequentialprocessing.

Step1isfirstexecuted,thenthesuccessmessagefromstep1isgiventostep2,andthenbothsuccessmessagesgotostep3,andthenallthreesuccessmessagesgotostep4,andsoon,suchthatthemessagessortofcollectandcascadedownthe"waterfall".

Consider:

functiondouble(done){

varargs=[].slice.call(arguments,1);

console.log(args);

setTimeout(function(){

done(args[args.length-1]*2);

},100);

}

ASQ(3)

.waterfall(

double,//[3]

double,//[6]

double,//[6,12]

double//[6,12,24]

)

.val(function(){

varargs=[].slice.call(arguments);

console.log(args);//[6,12,24,48]

});

Ifatanypointinthe"waterfall"anerroroccurs,thewholesequenceimmediatelygoesintoanerrorstate.

Sometimesyouwanttomanageerrorsatthesteplevelandnotletthemnecessarilysendthewholesequenceintotheerrorstate.asynquenceofferstwostepvariationsforthatpurpose.

try(..)attemptsastep,andifitsucceeds,thesequenceproceedsasnormal,butifthestepfails,thefailureisturnedintoasuccessmessageformatedas{catch:..}withtheerrormessage(s)filledin:

ASQ()

.try(success1)

.val(output)//1

ErrorTolerance

Page 149: You Don't Know JS: Async & Performance

.try(failure3)

.val(output)//{catch:3}

.or(function(err){

//nevergetshere

});

Youcouldinsteadsetuparetryloopusinguntil(..),whichtriesthestepandifitfails,retriesthestepagainonthenexteventlooptick,andsoon.

Thisretryloopcancontinueindefinitely,butifyouwanttobreakoutoftheloop,youcancallthebreak()flagonthecompletiontrigger,whichsendsthemainsequenceintoanerrorstate:

varcount=0;

ASQ(3)

.until(double)

.val(output)//6

.until(function(done){

count++;

setTimeout(function(){

if(count<5){

done.fail();

}

else{

//breakoutofthe`until(..)`retryloop

done.break("Oops");

}

},100);

})

.or(output);//Oops

Ifyouwouldprefertohave,inlineinyoursequence,Promise-stylesemanticslikePromises'then(..)andcatch(..)(seeChapter3),youcanusethepThenandpCatchplug-ins:

ASQ(21)

.pThen(function(msg){

returnmsg*2;

})

.pThen(output)//42

.pThen(function(){

//throwanexception

doesnt.Exist();

})

.pCatch(function(err){

//caughttheexception(rejection)

console.log(err);//ReferenceError

})

.val(function(){

//mainsequenceisbackina

//successstatebecauseprevious

//exceptionwascaughtby

//`pCatch(..)`

});

pThen(..)andpCatch(..)aredesignedtoruninthesequence,butbehaveasifitwasanormalPromisechain.Assuch,youcaneitherresolvegenuinePromisesorasynquencesequencesfromthe"fulfillment"handlerpassedtopThen(..)(seeChapter3).

OnefeaturethatcanbequiteusefulaboutPromisesisthatyoucanattachmultiplethen(..)handlerregistrationstothesamepromise,effectively"forking"theflow-controlatthatpromise:

Promise-StyleSteps

ForkingSequences

Page 150: You Don't Know JS: Async & Performance

varp=Promise.resolve(21);

//fork1(from`p`)

p.then(function(msg){

returnmsg*2;

})

.then(function(msg){

console.log(msg);//42

})

//fork2(from`p`)

p.then(function(msg){

console.log(msg);//21

});

Thesame"forking"iseasyinasynquencewithfork():

varsq=ASQ(..).then(..).then(..);

varsq2=sq.fork();

//fork1

sq.then(..)..;

//fork2

sq2.then(..)..;

Thereverseoffork()ing,youcancombinetwosequencesbysubsumingoneintoanother,usingtheseq(..)instancemethod:

varsq=ASQ(function(done){

setTimeout(function(){

done("HelloWorld");

},200);

});

ASQ(function(done){

setTimeout(done,100);

})

//subsume`sq`sequenceintothissequence

.seq(sq)

.val(function(msg){

console.log(msg);//HelloWorld

})

seq(..)caneitheracceptasequenceitself,asshownhere,orafunction.Ifafunction,it'sexpectedthatthefunctionwhencalledwillreturnasequence,sotheprecedingcodecouldhavebeendonewith:

//..

.seq(function(){

returnsq;

})

//..

Also,thatstepcouldinsteadhavebeenaccomplishedwithapipe(..):

//..

.then(function(done){

//pipe`sq`intothe`done`continuationcallback

sq.pipe(done);

})

//..

CombiningSequences

Page 151: You Don't Know JS: Async & Performance

Whenasequenceissubsumed,bothitssuccessmessagestreamanditserrorstreamarepipedin.

Note:Asmentionedinanearliernote,piping(manuallywithpipe(..)orautomaticallywithseq(..))optsthesourcesequenceoutoferror-reporting,butdoesn'taffecttheerrorreportingstatusofthetargetsequence.

Ifanystepofasequenceisjustanormalvalue,thatvalueisjustmappedtothatstep'scompletionmessage:

varsq=ASQ(42);

sq.val(function(msg){

console.log(msg);//42

});

Ifyouwanttomakeasequencethat'sautomaticallyerrored:

varsq=ASQ.failed("Oops");

ASQ()

.seq(sq)

.val(function(msg){

//won'tgethere

})

.or(function(err){

console.log(err);//Oops

});

Youalsomaywanttoautomaticallycreateadelayed-valueoradelayed-errorsequence.UsingtheafterandfailAftercontribplug-ins,thisiseasy:

varsq1=ASQ.after(100,"Hello","World");

varsq2=ASQ.failAfter(100,"Oops");

sq1.val(function(msg1,msg2){

console.log(msg1,msg2);//HelloWorld

});

sq2.or(function(err){

console.log(err);//Oops

});

Youcanalsoinsertadelayinthemiddleofasequenceusingafter(..):

ASQ(42)

//insertadelayintothesequence

.after(100)

.val(function(msg){

console.log(msg);//42

});

IthinkasynquencesequencesprovidealotofvalueontopofnativePromises,andforthemostpartyou'llfinditmorepleasantandmorepowerfultoworkatthatlevelofabstration.However,integratingasynquencewithothernon-asynquencecodewillbeareality.

Youcaneasilysubsumeapromise(e.g.,thenable--seeChapter3)intoasequenceusingthepromise(..)instancemethod:

ValueandErrorSequences

PromisesandCallbacks

Page 152: You Don't Know JS: Async & Performance

varp=Promise.resolve(42);

ASQ()

.promise(p)//couldalso:`function(){returnp;}`

.val(function(msg){

console.log(msg);//42

});

Andtogotheoppositedirectionandfork/vendapromisefromasequenceatacertainstep,usethetoPromisecontribplug-in:

varsq=ASQ.after(100,"HelloWorld");

sq.toPromise()

//thisisastandardpromisechainnow

.then(function(msg){

returnmsg.toUpperCase();

})

.then(function(msg){

console.log(msg);//HELLOWORLD

});

Toadaptasynquencetosystemsusingcallbacks,thereareseveralhelperfacilities.Toautomaticallygeneratean"error-firststyle"callbackfromyoursequencetowireintoacallback-orientedutility,useerrfcb:

varsq=ASQ(function(done){

//note:expecting"error-firststyle"callback

someAsyncFuncWithCB(1,2,done.errfcb)

})

.val(function(msg){

//..

})

.or(function(err){

//..

});

//note:expecting"error-firststyle"callback

anotherAsyncFuncWithCB(1,2,sq.errfcb());

Youalsomaywanttocreateasequence-wrappedversionofautility--compareto"promisory"inChapter3and"thunkory"inChapter4--andasynquenceprovidesASQ.wrap(..)forthatpurpose:

varcoolUtility=ASQ.wrap(someAsyncFuncWithCB);

coolUtility(1,2)

.val(function(msg){

//..

})

.or(function(err){

//..

});

Note:Forthesakeofclarity(andforfun!),let'scoinyetanotherterm,forasequence-producingfunctionthatcomesfromASQ.wrap(..),likecoolUtilityhere.Ipropose"sequory"("sequence"+"factory").

Thenormalparadigmforasequenceisthateachstepisresponsibleforcompletingitself,whichiswhatadvancesthesequence.Promisesworkthesameway.

TheunfortunatepartisthatsometimesyouneedexternalcontroloveraPromise/step,whichleadstoawkward"capabilityextraction".

IterableSequences

Page 153: You Don't Know JS: Async & Performance

ConsiderthisPromisesexample:

vardomready=newPromise(function(resolve,reject){

//don'twanttoputthishere,because

//itbelongslogicallyinanotherpart

//ofthecode

document.addEventListener("DOMContentLoaded",resolve);

});

//..

domready.then(function(){

//DOMisready!

});

The"capabilityextraction"anti-patternwithPromiseslookslikethis:

varready;

vardomready=newPromise(function(resolve,reject){

//extractthe`resolve()`capability

ready=resolve;

});

//..

domready.then(function(){

//DOMisready!

});

//..

document.addEventListener("DOMContentLoaded",ready);

Note:Thisanti-patternisanawkwardcodesmell,inmyopinion,butsomedeveloperslikeit,forreasonsIcan'tgrasp.

asynquenceoffersaninvertedsequencetypeIcall"iterablesequences",whichexternalizesthecontrolcapability(it'squiteusefulinusecaseslikethedomready):

//note:`domready`hereisan*iterator*that

//controlsthesequence

vardomready=ASQ.iterable();

//..

domready.val(function(){

//DOMisready

});

//..

document.addEventListener("DOMContentLoaded",domready.next);

There'smoretoiterablesequencesthanwhatweseeinthisscenario.We'llcomebacktotheminAppendixB.

InChapter4,wederivedautilitycalledrun(..)whichcanrungeneratorstocompletion,listeningforyieldedPromisesandusingthemtoasyncresumethegenerator.asynquencehasjustsuchautilitybuiltin,calledrunner(..).

Let'sfirstsetupsomehelpersforillustration:

functiondoublePr(x){

returnnewPromise(function(resolve,reject){

setTimeout(function(){

RunningGenerators

Page 154: You Don't Know JS: Async & Performance

resolve(x*2);

},100);

});

}

functiondoubleSeq(x){

returnASQ(function(done){

setTimeout(function(){

done(x*2)

},100);

});

}

Now,wecanuserunner(..)asastepinthemiddleofasequence:

ASQ(10,11)

.runner(function*(token){

varx=token.messages[0]+token.messages[1];

//yieldarealpromise

x=yielddoublePr(x);

//yieldasequence

x=yielddoubleSeq(x);

returnx;

})

.val(function(msg){

console.log(msg);//84

});

Youcanalsocreateaself-packagedgenerator--thatis,anormalfunctionthatrunsyourspecifiedgeneratorandreturnsasequenceforitscompletion--byASQ.wrap(..)ingit:

varfoo=ASQ.wrap(function*(token){

varx=token.messages[0]+token.messages[1];

//yieldarealpromise

x=yielddoublePr(x);

//yieldasequence

x=yielddoubleSeq(x);

returnx;

},{gen:true});

//..

foo(8,9)

.val(function(msg){

console.log(msg);//68

});

There'salotmoreawesomethatrunner(..)iscapableof,butwe'llcomebacktothatinAppendixB.

asynquenceisasimpleabstraction--asequenceisaseriesof(async)steps--ontopofPromises,aimedatmakingworkingwithvariousasynchronouspatternsmucheasier,withoutanycompromiseincapability.

ThereareothergoodiesintheasynquencecoreAPIanditscontribplug-insbeyondwhatwesawinthisappendix,butwe'llleavethatasanexerciseforthereadertogochecktherestofthecapabilitiesout.

You'venowseentheessenceandspiritofasynquence.Thekeytakeawayisthatasequenceiscomprisedofsteps,and

WrappedGenerators

Review

Page 155: You Don't Know JS: Async & Performance

thosestepscanbeanyofdozensofdifferentvariationsonPromises,ortheycanbeagenerator-run,or...Thechoiceisuptoyou,youhaveallthefreedomtoweavetogetherwhateverasyncflowcontrollogicisappropriateforyourtasks.Nomorelibraryswitchingtocatchdifferentasyncpatterns.

Iftheseasynquencesnippetshavemadesensetoyou,you'renowprettywelluptospeedonthelibrary;itdoesn'ttakethatmuchtolearn,actually!

Ifyou'restillalittlefuzzyonhowitworks(orwhy!),you'llwanttospendalittlemoretimeexaminingthepreviousexamplesandplayingaroundwithasynquenceyourself,beforegoingontothenextappendix.AppendixBwillpushasynquenceintoseveralmoreadvancedandpowerfulasyncpatterns.

Page 156: You Don't Know JS: Async & Performance

AppendixAintroducedtheasynquencelibraryforsequence-orientedasyncflowcontrol,primarilybasedonPromisesandgenerators.

Nowwe'llexploreotheradvancedasynchronouspatternsbuiltontopofthatexistingunderstandingandfunctionality,andseehowasynquencemakesthosesophisticatedasynctechniqueseasytomixandmatchinourprogramswithoutneedinglotsofseparatelibraries.

Weintroducedasynquence'siterablesequencesinthepreviousappendix,butwewanttorevisittheminmoredetail.

Torefresh,recall:

vardomready=ASQ.iterable();

//..

domready.val(function(){

//DOMisready

});

//..

document.addEventListener("DOMContentLoaded",domready.next);

Now,let'sdefineasequenceofmultiplestepsasaniterablesequence:

varsteps=ASQ.iterable();

steps

.then(functionSTEP1(x){

returnx*2;

})

.steps(functionSTEP2(x){

returnx+3;

})

.steps(functionSTEP3(x){

returnx*4;

});

steps.next(8).value;//16

steps.next(16).value;//19

steps.next(19).value;//76

steps.next().done;//true

Asyoucansee,aniterablesequenceisastandard-compliantiterator(seeChapter4).So,itcanbeiteratedwithanES6for..ofloop,justlikeagenerator(oranyotheriterable)can:

varsteps=ASQ.iterable();

steps

.then(functionSTEP1(){return2;})

.then(functionSTEP2(){return4;})

.then(functionSTEP3(){return6;})

.then(functionSTEP4(){return8;})

.then(functionSTEP5(){return10;});

YouDon'tKnowJS:Async&Performance

AppendixB:AdvancedAsyncPatterns

IterableSequences

Page 157: You Don't Know JS: Async & Performance

for(varvofsteps){

console.log(v);

}

//246810

Beyondtheeventtriggeringexampleshowninthepreviousappendix,iterablesequencesareinterestingbecauseinessencetheycanbeseenasastand-inforgeneratorsorPromisechains,butwithevenmoreflexibility.

ConsideramultipleAjaxrequestexample--we'veseenthesamescenarioinChapters3and4,bothasaPromisechainandasagenerator,respectively--expressedasaniterablesequence:

//sequence-awareajax

varrequest=ASQ.wrap(ajax);

ASQ("http://some.url.1")

.runner(

ASQ.iterable()

.then(functionSTEP1(token){

varurl=token.messages[0];

returnrequest(url);

})

.then(functionSTEP2(resp){

returnASQ().gate(

request("http://some.url.2/?v="+resp),

request("http://some.url.3/?v="+resp)

);

})

.then(functionSTEP3(r1,r2){returnr1+r2;})

)

.val(function(msg){

console.log(msg);

});

Theiterablesequenceexpressesasequentialseriesof(syncorasync)stepsthatlooksawfullysimilartoaPromisechain--inotherwords,it'smuchcleanerlookingthanjustplainnestedcallbacks,butnotquiteasniceastheyield-basedsequentialsyntaxofgenerators.

ButwepasstheiterablesequenceintoASQ#runner(..),whichrunsittocompletionthesameasifitwasagenerator.Thefactthataniterablesequencebehavesessentiallythesameasageneratorisnotableforacoupleofreasons.

First,iterablesequencesarekindofapre-ES6equivalenttoacertainsubsetofES6generators,whichmeansyoucaneitherauthorthemdirectly(torunanywhere),oryoucanauthorES6generatorsandtranspile/convertthemtoiterablesequences(orPromisechainsforthatmatter!).

Thinkingofanasync-run-to-completiongeneratorasjustsyntacticsugarforaPromisechainisanimportantrecognitionoftheirisomorphicrelationship.

Beforewemoveon,weshouldnotethattheprevioussnippetcouldhavebeenexpressedinasynquenceas:

ASQ("http://some.url.1")

.seq(/*STEP1*/request)

.seq(functionSTEP2(resp){

returnASQ().gate(

request("http://some.url.2/?v="+resp),

request("http://some.url.3/?v="+resp)

);

})

.val(functionSTEP3(r1,r2){returnr1+r2;})

.val(function(msg){

console.log(msg);

});

Moreover,step2couldhaveevenbeenexpressedas:

Page 158: You Don't Know JS: Async & Performance

.gate(

functionSTEP2a(done,resp){

request("http://some.url.2/?v="+resp)

.pipe(done);

},

functionSTEP2b(done,resp){

request("http://some.url.3/?v="+resp)

.pipe(done);

}

)

So,whywouldwegotothetroubleofexpressingourflowcontrolasaniterablesequenceinaASQ#runner(..)step,whenitseemslikeasimpler/flatterasyquencechaindoesthejobwell?

Becausetheiterablesequenceformhasanimportanttrickupitssleevethatgivesusmorecapability.Readon.

Generators,normalasynquencesequences,andPromisechains,arealleagerlyevaluated--whateverflowcontrolisexpressedinitiallyisthefixedflowthatwillbefollowed.

However,iterablesequencesarelazilyevaluated,whichmeansthatduringexecutionoftheiterablesequence,youcanextendthesequencewithmorestepsifdesired.

Note:Youcanonlyappendtotheendofaniterablesequence,notinjectintothemiddleofthesequence.

Let'sfirstlookatasimpler(synchronous)exampleofthatcapabilitytogetfamiliarwithit:

functiondouble(x){

x*=2;

//shouldwekeepextending?

if(x<500){

isq.then(double);

}

returnx;

}

//setupsingle-stepiterablesequence

varisq=ASQ.iterable().then(double);

for(varv=10,ret;

(ret=isq.next(v))&&!ret.done;

){

v=ret.value;

console.log(v);

}

Theiterablesequencestartsoutwithonlyonedefinedstep(isq.then(double)),butthesequencekeepsextendingitselfundercertainconditions(x<500).BothasynquencesequencesandPromisechainstechnicallycandosomethingsimilar,butwe'llseeinalittlebitwhytheircapabilityisinsufficient.

Thoughthisexampleisrathertrivialandcouldotherwisebeexpressedwithawhileloopinagenerator,we'llconsidermoresophisticatedcases.

Forinstance,youcouldexaminetheresponsefromanAjaxrequestandifitindicatesthatmoredataisneeded,youconditionallyinsertmorestepsintotheiterablesequencetomaketheadditionalrequest(s).Oryoucouldconditionallyaddavalue-formattingsteptotheendofyourAjaxhandling.

Consider:

varsteps=ASQ.iterable()

ExtendingIterableSequences

Page 159: You Don't Know JS: Async & Performance

.then(functionSTEP1(token){

varurl=token.messages[0].url;

//wasanadditionalformattingstepprovided?

if(token.messages[0].format){

steps.then(token.messages[0].format);

}

returnrequest(url);

})

.then(functionSTEP2(resp){

//addanotherAjaxrequesttothesequence?

if(/x1/.test(resp)){

steps.then(functionSTEP5(text){

returnrequest(

"http://some.url.4/?v="+text

);

});

}

returnASQ().gate(

request("http://some.url.2/?v="+resp),

request("http://some.url.3/?v="+resp)

);

})

.then(functionSTEP3(r1,r2){returnr1+r2;});

Youcanseeintwodifferentplaceswhereweconditionallyextendstepswithsteps.then(..).Andtorunthisstepsiterablesequence,wejustwireitintoourmainprogramflowwithanasynquencesequence(calledmainhere)usingASQ#runner(..):

varmain=ASQ({

url:"http://some.url.1",

format:functionSTEP4(text){

returntext.toUpperCase();

}

})

.runner(steps)

.val(function(msg){

console.log(msg);

});

Cantheflexibility(conditionalbehavior)ofthestepsiterablesequencebeexpressedwithagenerator?Kindof,butwehavetorearrangethelogicinaslightlyawkwardway:

function*steps(token){

//**STEP1**

varresp=yieldrequest(token.messages[0].url);

//**STEP2**

varrvals=yieldASQ().gate(

request("http://some.url.2/?v="+resp),

request("http://some.url.3/?v="+resp)

);

//**STEP3**

vartext=rvals[0]+rvals[1];

//**STEP4**

//wasanadditionalformattingstepprovided?

if(token.messages[0].format){

text=yieldtoken.messages[0].format(text);

}

//**STEP5**

//needanotherAjaxrequestaddedtothesequence?

if(/foobar/.test(resp)){

text=yieldrequest(

"http://some.url.4/?v="+text

);

}

Page 160: You Don't Know JS: Async & Performance

returntext;

}

//note:`*steps()`canberunbythesame`ASQ`sequence

//as`steps`waspreviously

Settingasidethealreadyidentifiedbenefitsofthesequential,synchronous-lookingsyntaxofgenerators(seeChapter4),thestepslogichadtobereorderedinthe*steps()generatorform,tofakethedynamicismoftheextendableiterablesequencesteps.

WhataboutexpressingthefunctionalitywithPromisesorsequences,though?Youcandosomethinglikethis:

varsteps=something(..)

.then(..)

.then(function(..){

//..

//extendingthechain,right?

steps=steps.then(..);

//..

})

.then(..);

Theproblemissubtlebutimportanttograsp.So,considertryingtowireupourstepsPromisechainintoourmainprogramflow--thistimeexpressedwithPromisesinsteadofasynquence:

varmain=Promise.resolve({

url:"http://some.url.1",

format:functionSTEP4(text){

returntext.toUpperCase();

}

})

.then(function(..){

returnsteps;//hint!

})

.val(function(msg){

console.log(msg);

});

Canyouspottheproblemnow?Lookclosely!

There'saraceconditionforsequencestepsordering.Whenyoureturnsteps,atthatmomentstepsmightbetheoriginallydefinedpromisechain,oritmightnowpointtotheextendedpromisechainviathesteps=steps.then(..)call,dependingonwhatorderthingshappen.

Herearethetwopossibleoutcomes:

Ifstepsisstilltheoriginalpromisechain,onceit'slater"extended"bysteps=steps.then(..),thatextendedpromiseontheendofthechainisnotconsideredbythemainflow,asit'salreadytappedthestepschain.Thisistheunfortunatelylimitingeagerevaluation.Ifstepsisalreadytheextendedpromisechain,itworksasweexpectinthattheextendedpromiseiswhatmaintaps.

Otherthantheobviousfactthataraceconditionisintolerable,thefirstcaseistheconcern;itillustrateseagerevaluationofthepromisechain.Bycontrast,weeasilyextendedtheiterablesequencewithoutsuchissues,becauseiterablesequencesarelazilyevaluated.

Themoredynamicyouneedyourflowcontrol,themoreiterablesequenceswillshine.

Tip:Checkoutmoreinformationandexamplesofiterablesequencesontheasynquencesite(https://github.com/getify/asynquence/blob/master/README.md#iterable-sequences).

Page 161: You Don't Know JS: Async & Performance

Itshouldbeobviousfrom(atleast!)Chapter3thatPromisesareaverypowerfultoolinyourasynctoolbox.Butonethingthat'sclearlylackingisintheircapabilitytohandlestreamsofevents,asaPromisecanonlyberesolvedonce.Andfrankly,thisexactsameweaknessistrueofplainasynquencesequences,aswell.

Considerascenariowhereyouwanttofireoffaseriesofstepseverytimeacertaineventisfired.AsinglePromiseorsequencecannotrepresentalloccurrencesofthatevent.So,youhavetocreateawholenewPromisechain(orsequence)foreacheventoccurrence,suchas:

listener.on("foobar",function(data){

//createaneweventhandlingpromisechain

newPromise(function(resolve,reject){

//..

})

.then(..)

.then(..);

});

Thebasefunctionalityweneedispresentinthisapproach,butit'sfarfromadesirablewaytoexpressourintendedlogic.Therearetwoseparatecapabilitiesconflatedinthisparadigm:theeventlistening,andrespondingtotheevent;separationofconcernswouldimploreustoseparateoutthesecapabilities.

ThecarefullyobservantreaderwillseethisproblemassomewhatsymmetricaltotheproblemswedetailedwithcallbacksinChapter2;it'skindofaninversionofcontrolproblem.

Imagineuninvertingthisparadigm,likeso:

varobservable=listener.on("foobar");

//later

observable

.then(..)

.then(..);

//elsewhere

observable

.then(..)

.then(..);

TheobservablevalueisnotexactlyaPromise,butyoucanobserveitmuchlikeyoucanobserveaPromise,soit'scloselyrelated.Infact,itcanbeobservedmanytimes,anditwillsendoutnotificationseverytimeitsevent("foobar")occurs.

Tip:ThispatternI'vejustillustratedisamassivesimplificationoftheconceptsandmotivationsbehindreactiveprogramming(akaRP),whichhasbeenimplemented/expoundeduponbyseveralgreatprojectsandlanguages.AvariationonRPisfunctionalreactiveprogramming(FRP),whichreferstoapplyingfunctionalprogrammingtechniques(immutability,referentialintegrity,etc.)tostreamsofdata."Reactive"referstospreadingthisfunctionalityoutovertimeinresponsetoevents.Theinterestedreadershouldconsiderstudying"ReactiveObservables"inthefantastic"ReactiveExtensions"library("RxJS"forJavaScript)byMicrosoft(http://reactive-extensions.github.io/RxJS/);it'smuchmoresophisticatedandpowerfulthanI'vejustshown.Also,AndreStaltzhasanexcellentwrite-up(https://gist.github.com/staltz/868e7e9bc2a7b8c1f754)thatpragmaticallylaysoutRPinconcreteexamples.

Atthetimeofthiswriting,there'sanearlyES7proposalforanewdatatypecalled"Observable"(https://github.com/jhusain/asyncgenerator#introducing-observable),whichinspiritissimilartowhatwe'velaidouthere,butisdefinitelymoresophisticated.

EventReactive

ES7Observables

Page 162: You Don't Know JS: Async & Performance

ThenotionofthiskindofObservableisthatthewayyou"subscribe"totheeventsfromastreamistopassinagenerator--actuallytheiteratoristheinterestedparty--whosenext(..)methodwillbecalledforeachevent.

Youcouldimagineitsortoflikethis:

//`someEventStream`isastreamofevents,likefrom

//mouseclicks,andthelike.

varobserver=newObserver(someEventStream,function*(){

while(varevt=yield){

console.log(evt);

}

});

Thegeneratoryoupassinwillyieldpausethewhileloopwaitingforthenextevent.Theiteratorattachedtothegeneratorinstancewillhaveitsnext(..)calledeachtimesomeEventStreamhasaneweventpublished,andsothateventdatawillresumeyourgenerator/iteratorwiththeevtdata.

Inthesubscriptiontoeventsfunctionalityhere,it'stheiteratorpartthatmatters,notthegenerator.Soconceptuallyyoucouldpassinpracticallyanyiterable,includingASQ.iterable()iterablesequences.

Interestingly,therearealsoproposedadapterstomakeiteasytoconstructObservablesfromcertaintypesofstreams,suchasfromEvent(..)forDOMevents.IfyoulookatasuggestedimplementationoffromEvent(..)intheearlierlinkedES7proposal,itlooksanawfullotliketheASQ.react(..)we'llseeinthenextsection.

Ofcourse,theseareallearlyproposals,sowhatshakesoutmayverywelllook/behavedifferentlythanshownhere.Butit'sexcitingtoseetheearlyalignmentsofconceptsacrossdifferentlibrariesandlanguageproposals!

WiththatcrazybriefsummaryofObservables(andF/RP)asourinspirationandmotivation,Iwillnowillustrateanadaptationofasmallsubsetof"ReactiveObservables,"whichIcall"ReactiveSequences."

First,let'sstartwithhowtocreateanObservable,usinganasynquenceplug-inutilitycalledreact(..):

varobservable=ASQ.react(functionsetup(next){

listener.on("foobar",next);

});

Now,let'sseehowtodefineasequencethat"reacts"--inF/RP,thisistypicallycalled"subscribing"--tothatobservable:

observable

.seq(..)

.then(..)

.val(..);

So,youjustdefinethesequencebychainingofftheObservable.That'seasy,huh?

InF/RP,thestreamofeventstypicallychannelsthroughasetoffunctionaltransforms,likescan(..),map(..),reduce(..),andsoon.Withreactivesequences,eacheventchannelsthroughanewinstanceofthesequence.Let'slookatamoreconcreteexample:

ASQ.react(functionsetup(next){

document.getElementById("mybtn")

.addEventListener("click",next,false);

})

.seq(function(evt){

varbtnID=evt.target.id;

returnrequest(

ReactiveSequences

Page 163: You Don't Know JS: Async & Performance

"http://some.url.1/?id="+btnID

);

})

.val(function(text){

console.log(text);

});

The"reactive"portionofthereactivesequencecomesfromassigningoneormoreeventhandlerstoinvoketheeventtrigger(callingnext(..)).

The"sequence"portionofthereactivesequenceisexactlylikethesequenceswe'vealreadyexplored:eachstepcanbewhateverasynchronoustechniquemakessense,fromcontinuationcallbacktoPromisetogenerator.

Onceyousetupareactivesequence,itwillcontinuetoinitiateinstancesofthesequenceaslongastheeventskeepfiring.Ifyouwanttostopareactivesequence,youcancallstop().

Ifareactivesequenceisstop()'d,youlikelywanttheeventhandler(s)tobeunregisteredaswell;youcanregisterateardownhandlerforthispurpose:

varsq=ASQ.react(functionsetup(next,registerTeardown){

varbtn=document.getElementById("mybtn");

btn.addEventListener("click",next,false);

//willbecalledonce`sq.stop()`iscalled

registerTeardown(function(){

btn.removeEventListener("click",next,false);

});

})

.seq(..)

.then(..)

.val(..);

//later

sq.stop();

Note:Thethisbindingreferenceinsidethesetup(..)handleristhesamesqreactivesequence,soyoucanusethethisreferencetoaddtothereactivesequencedefinition,callmethodslikestop(),andsoon.

Here'sanexamplefromtheNode.jsworld,usingreactivesequencestohandleincomingHTTPrequests:

varserver=http.createServer();

server.listen(8000);

//reactiveobserver

varrequest=ASQ.react(functionsetup(next,registerTeardown){

server.addListener("request",next);

server.addListener("close",this.stop);

registerTeardown(function(){

server.removeListener("request",next);

server.removeListener("close",request.stop);

});

});

//respondtorequests

request

.seq(pullFromDatabase)

.val(function(data,res){

res.end(data);

});

//nodeteardown

process.on("SIGINT",request.stop);

Thenext(..)triggercanalsoadapttonodestreamseasily,usingonStream(..)andunStream(..):

Page 164: You Don't Know JS: Async & Performance

ASQ.react(functionsetup(next){

varfstream=fs.createReadStream("/some/file");

//pipethestream's"data"eventto`next(..)`

next.onStream(fstream);

//listenfortheendofthestream

fstream.on("end",function(){

next.unStream(fstream);

});

})

.seq(..)

.then(..)

.val(..);

Youcanalsousesequencecombinationstocomposemultiplereactivesequencestreams:

varsq1=ASQ.react(..).seq(..).then(..);

varsq2=ASQ.react(..).seq(..).then(..);

varsq3=ASQ.react(..)

.gate(

sq1,

sq2

)

.then(..);

ThemaintakeawayisthatASQ.react(..)isalightweightadaptationofF/RPconcepts,enablingthewiringofaneventstreamtoasequence,hencetheterm"reactivesequence."Reactivesequencesaregenerallycapableenoughforbasicreactiveuses.

Note:Here'sanexampleofusingASQ.react(..)inmanagingUIstate(http://jsbin.com/rozipaki/6/edit?js,output),andanotherexampleofhandlingHTTPrequest/responsestreamswithASQ.react(..)(https://gist.github.com/getify/bba5ec0de9d6047b720e).

HopefullyChapter4helpedyougetprettyfamiliarwithES6generators.Inparticular,wewanttorevisitthe"GeneratorConcurrency"discussion,andpushitevenfurther.

WeimaginedarunAll(..)utilitythatcouldtaketwoormoregeneratorsandrunthemconcurrently,lettingthemcooperativelyyieldcontrolfromonetothenext,withoptionalmessagepassing.

Inadditiontobeingabletorunasinglegeneratortocompletion,theASQ#runner(..)wediscussedinAppendixAisasimilarimplementationoftheconceptsofrunAll(..),whichcanrunmultiplegeneratorsconcurrentlytocompletion.

Solet'sseehowwecanimplementtheconcurrentAjaxscenariofromChapter4:

ASQ(

"http://some.url.2"

)

.runner(

function*(token){

//transfercontrol

yieldtoken;

varurl1=token.messages[0];//"http://some.url.1"

//clearoutmessagestostartfresh

token.messages=[];

varp1=request(url1);

//transfercontrol

yieldtoken;

GeneratorCoroutine

Page 165: You Don't Know JS: Async & Performance

token.messages.push(yieldp1);

},

function*(token){

varurl2=token.messages[0];//"http://some.url.2"

//messagepassandtransfercontrol

token.messages[0]="http://some.url.1";

yieldtoken;

varp2=request(url2);

//transfercontrol

yieldtoken;

token.messages.push(yieldp2);

//passalongresultstonextsequencestep

returntoken.messages;

}

)

.val(function(res){

//`res[0]`comesfrom"http://some.url.1"

//`res[1]`comesfrom"http://some.url.2"

});

ThemaindifferencesbetweenASQ#runner(..)andrunAll(..)areasfollows:

Eachgenerator(coroutine)isprovidedanargumentwecalltoken,whichisthespecialvaluetoyieldwhenyouwanttoexplicitlytransfercontroltothenextcoroutine.token.messagesisanarraythatholdsanymessagespassedinfromtheprevioussequencestep.It'salsoadatastructurethatyoucanusetosharemessagesbetweencoroutines.yieldingaPromise(orsequence)valuedoesnottransfercontrol,butinsteadpausesthecoroutineprocessinguntilthatvalueisready.Thelastreturnedoryieldedvaluefromthecoroutineprocessingrunwillbeforwardpassedtothenextstepinthesequence.

It'salsoeasytolayerhelpersontopofthebaseASQ#runner(..)functionalitytosuitdifferentuses.

Oneexamplethatmaybefamiliartomanyprogrammersisstatemachines.Youcan,withthehelpofasimplecosmeticutility,createaneasy-to-expressstatemachineprocessor.

Let'simaginesuchautility.We'llcallitstate(..),andwillpassittwoarguments:astatevalueandageneratorthathandlesthatstate.state(..)willdothedirtyworkofcreatingandreturninganadaptergeneratortopasstoASQ#runner(..).

Consider:

functionstate(val,handler){

//makeacoroutinehandlerforthisstate

returnfunction*(token){

//statetransitionhandler

functiontransition(to){

token.messages[0]=to;

}

//setinitialstate(ifnonesetyet)

if(token.messages.length<1){

token.messages[0]=val;

}

//keepgoinguntilfinalstate(false)isreached

while(token.messages[0]!==false){

//currentstatematchesthishandler?

if(token.messages[0]===val){

//delegatetostatehandler

yield*handler(transition);

StateMachines

Page 166: You Don't Know JS: Async & Performance

}

//transfercontroltoanotherstatehandler?

if(token.messages[0]!==false){

yieldtoken;

}

}

};

}

Ifyoulookclosely,you'llseethatstate(..)returnsbackageneratorthatacceptsatoken,andthenitsetsupawhileloopthatwillrununtilthestatemachinereachesitsfinalstate(whichwearbitrarilypickasthefalsevalue);that'sexactlythekindofgeneratorwewanttopasstoASQ#runner(..)!

Wealsoarbitrarilyreservethetoken.messages[0]slotastheplacewherethecurrentstateofourstatemachinewillbetracked,whichmeanswecanevenseedtheinitialstateasthevaluepassedinfromthepreviousstepinthesequence.

Howdoweusethestate(..)helperalongwithASQ#runner(..)?

varprevState;

ASQ(

/*optional:initialstatevalue*/

2

)

//runourstatemachine

//transitions:2->3->1->3->false

.runner(

//state`1`handler

state(1,function*stateOne(transition){

console.log("instate1");

prevState=1;

yieldtransition(3);//gotostate`3`

}),

//state`2`handler

state(2,function*stateTwo(transition){

console.log("instate2");

prevState=2;

yieldtransition(3);//gotostate`3`

}),

//state`3`handler

state(3,function*stateThree(transition){

console.log("instate3");

if(prevState===2){

prevState=3;

yieldtransition(1);//gotostate`1`

}

//alldone!

else{

yield"That'sallfolks!";

prevState=3;

yieldtransition(false);//terminalstate

}

})

)

//statemachinecomplete,somoveon

.val(function(msg){

console.log(msg);//That'sallfolks!

});

It'simportanttonotethatthe*stateOne(..),*stateTwo(..),and*stateThree(..)generatorsthemselvesarereinvokedeachtimethatstateisentered,andtheyfinishwhenyoutransition(..)toanothervalue.Whilenotshownhere,ofcoursethesestategeneratorhandlerscanbeasynchronouslypausedbyyieldingPromises/sequences/thunks.

Theunderneathhiddengeneratorsproducedbythestate(..)helperandactuallypassedtoASQ#runner(..)aretheonesthatcontinuetorunconcurrentlyforthelengthofthestatemachine,andeachofthemhandlescooperativelyyielding

Page 167: You Don't Know JS: Async & Performance

controltothenext,andsoon.

Note:Seethis"pingpong"example(http://jsbin.com/qutabu/1/edit?js,output)formoreillustrationofusingcooperativeconcurrencywithgeneratorsdrivenbyASQ#runner(..).

"CommunicatingSequentialProcesses"(CSP)wasfirstdescribedbyC.A.R.Hoareina1978academicpaper(http://dl.acm.org/citation.cfm?doid=359576.359585),andlaterina1985book(http://www.usingcsp.com/)ofthesamename.CSPdescribesaformalmethodforconcurrent"processes"tointeract(aka"communicate")duringprocessing.

Youmayrecallthatweexaminedconcurrent"processes"backinChapter1,soourexplorationofCSPherewillbuilduponthatunderstanding.

Likemostgreatconceptsincomputerscience,CSPisheavilysteepedinacademicformalism,expressedasaprocessalgebra.However,Isuspectsymbolicalgebratheoremswon'tmakemuchpracticaldifferencetothereader,sowewillwanttofindsomeotherwayofwrappingourbrainsaroundCSP.

IwillleavemuchoftheformaldescriptionandproofofCSPtoHoare'swriting,andtomanyotherfantasticwritingssince.Instead,wewilltrytojustbrieflyexplaintheideaofCSPinasun-academicandhopefullyintuitivelyunderstandableawayaspossible.

ThecoreprincipleinCSPisthatallcommunication/interactionbetweenotherwiseindependentprocessesmustbethroughformalmessagepassing.Perhapscountertoyourexpectations,CSPmessagepassingisdescribedasasynchronousaction,wherethesenderprocessandthereceiverprocesshavetomutuallybereadyforthemessagetobepassed.

HowcouldsuchsynchronousmessagingpossiblyberelatedtoasynchronousprogramminginJavaScript?

TheconcretenessofrelationshipcomesfromthenatureofhowES6generatorsareusedtoproducesynchronous-lookingactionsthatunderthecoverscanindeedeitherbesynchronousor(morelikely)asynchronous.

Inotherwords,twoormoreconcurrentlyrunninggeneratorscanappeartosynchronouslymessageeachotherwhilepreservingthefundamentalasynchronyofthesystembecauseeachgenerator'scodeispaused(aka"blocked")waitingonresumptionofanasynchronousaction.

Howdoesthiswork?

Imagineagenerator(aka"process")called"A"thatwantstosendamessagetogenerator"B."First,"A"yieldsthemessage(thuspausing"A")tobesentto"B."When"B"isreadyandtakesthemessage,"A"isthenresumed(unblocked).

Symmetrically,imagineagenerator"A"thatwantsamessagefrom"B.""A"yieldsitsrequest(thuspausing"A")forthemessagefrom"B,"andonce"B"sendsamessage,"A"takesthemessageandisresumed.

OneofthemorepopularexpressionsofthisCSPmessagepassingtheorycomesfromClojureScript'score.asynclibrary,andalsofromthegolanguage.ThesetakesonCSPembodythedescribedcommunicationsemanticsinaconduitthatisopenedbetweenprocessescalleda"channel."

Note:Thetermchannelisusedinpartbecausetherearemodesinwhichmorethanonevaluecanbesentatonceintothe"buffer"ofthechannel;thisissimilartowhatyoumaythinkofasastream.Wewon'tgointodepthaboutithere,butitcanbeaverypowerfultechniqueformanagingstreamsofdata.

InthesimplestnotionofCSP,achannelthatwecreatebetween"A"and"B"wouldhaveamethodcalledtake(..)forblockingtoreceiveavalue,andamethodcalledput(..)forblockingtosendavalue.

Thismightlooklike:

CommunicatingSequentialProcesses(CSP)

MessagePassing

Page 168: You Don't Know JS: Async & Performance

varch=channel();

function*foo(){

varmsg=yieldtake(ch);

console.log(msg);

}

function*bar(){

yieldput(ch,"HelloWorld");

console.log("messagesent");

}

run(foo);

run(bar);

//HelloWorld

//"messagesent"

Comparethisstructured,synchronous(-looking)messagepassinginteractiontotheinformalandunstructuredmessagesharingthatASQ#runner(..)providesthroughthetoken.messagesarrayandcooperativeyielding.Inessence,yieldput(..)isasingleoperationthatbothsendsthevalueandpausesexecutiontotransfercontrol,whereasinearlierexampleswedidthoseasseparatesteps.

Moreover,CSPstressesthatyoudon'treallyexplicitly"transfercontrol,"butratheryoudesignyourconcurrentroutinestoblockexpectingeitheravaluereceivedfromthechannel,ortoblockexpectingtotrytosendamessageonthechannel.Theblockingaroundreceivingorsendingmessagesishowyoucoordinatesequencingofbehaviorbetweenthecoroutines.

Note:Fairwarning:thispatternisverypowerfulbutit'salsoalittlemindtwistingtogetusedtoatfirst.Youwillwanttopracticethisabittogetusedtothisnewwayofthinkingaboutcoordinatingyourconcurrency.

ThereareseveralgreatlibrariesthathaveimplementedthisflavorofCSPinJavaScript,mostnotably"js-csp"(https://github.com/ubolonton/js-csp),whichJamesLong(http://twitter.com/jlongster)forked(https://github.com/jlongster/js-csp)andhaswrittenextensivelyabout(http://jlongster.com/Taming-the-Asynchronous-Beast-with-CSP-in-JavaScript).Also,itcannotbestressedenoughhowamazingthemanywritingsofDavidNolen(http://twitter.com/swannodette)areonthetopicofadaptingClojureScript'sgo-stylecore.asyncCSPintoJSgenerators(http://swannodette.github.io/2013/08/24/es6-generators-and-csp/).

Becausewe'vebeendiscussingasyncpatternshereinthecontextofmyasynquencelibrary,youmightbeinterestedtoseethatwecanfairlyeasilyaddanemulationlayerontopofASQ#runner(..)generatorhandlingasanearlyperfectportingoftheCSPAPIandbehavior.Thisemulationlayershipsasanoptionalpartofthe"asynquence-contrib"packagealongsideasynquence.

Verysimilartothestate(..)helperfromearlier,ASQ.csp.go(..)takesagenerator--ingo/core.asyncterms,it'sknownasagoroutine--andadaptsittousewithASQ#runner(..)byreturninganewgenerator.

Insteadofbeingpassedatoken,yourgoroutinereceivesaninitiallycreatedchannel(chbelow)thatallgoroutinesinthisrunwillshare.Youcancreatemorechannels(whichisoftenquitehelpful!)withASQ.csp.chan(..).

InCSP,wemodelallasynchronyintermsofblockingonchannelmessages,ratherthanblockingwaitingforaPromise/sequence/thunktocomplete.

So,insteadofyieldingthePromisereturnedfromrequest(..),request(..)shouldreturnachannelthatyoutake(..)avaluefrom.Inotherwords,asingle-valuechannelisroughlyequivalentinthiscontext/usagetoaPromise/sequence.

Let'sfirstmakeachannel-awareversionofrequest(..):

functionrequest(url){

varch=ASQ.csp.channel();

ajax(url).then(function(content){

asynquenceCSPemulation

Page 169: You Don't Know JS: Async & Performance

//`putAsync(..)`isaversionof`put(..)`that

//canbeusedoutsideofagenerator.Itreturns

//apromisefortheoperation'scompletion.We

//don'tusethatpromisehere,butwecouldif

//weneededtobenotifiedwhenthevaluehad

//been`take(..)`n.

ASQ.csp.putAsync(ch,content);

});

returnch;

}

FromChapter3,"promisory"isaPromise-producingutility,"thunkory"fromChapter4isathunk-producingutility,andfinally,inAppendixAweinvented"sequory"forasequence-producingutility.

Naturally,weneedtocoinasymmetrictermhereforachannel-producingutility.Solet'sunsurprisinglycallita"chanory"("channel"+"factory").Asanexerciseforthereader,tryyourhandatdefiningachannelify(..)utilitysimilartoPromise.wrap(..)/promisify(..)(Chapter3),thunkify(..)(Chapter4),andASQ.wrap(..)(AppendixA).

NowconsidertheconcurrentAjaxexampleusingasyquence-flavoredCSP:

ASQ()

.runner(

ASQ.csp.go(function*(ch){

yieldASQ.csp.put(ch,"http://some.url.2");

varurl1=yieldASQ.csp.take(ch);

//"http://some.url.1"

varres1=yieldASQ.csp.take(request(url1));

yieldASQ.csp.put(ch,res1);

}),

ASQ.csp.go(function*(ch){

varurl2=yieldASQ.csp.take(ch);

//"http://some.url.2"

yieldASQ.csp.put(ch,"http://some.url.1");

varres2=yieldASQ.csp.take(request(url2));

varres1=yieldASQ.csp.take(ch);

//passalongresultstonextsequencestep

ch.buffer_size=2;

ASQ.csp.put(ch,res1);

ASQ.csp.put(ch,res2);

})

)

.val(function(res1,res2){

//`res1`comesfrom"http://some.url.1"

//`res2`comesfrom"http://some.url.2"

});

ThemessagepassingthattradestheURLstringsbetweenthetwogoroutinesisprettystraightforward.ThefirstgoroutinemakesanAjaxrequesttothefirstURL,andthatresponseisputontothechchannel.ThesecondgoroutinemakesanAjaxrequesttothesecondURL,thengetsthefirstresponseres1offthechchannel.Atthatpoint,bothresponsesres1andres2arecompletedandready.

Ifthereareanyremainingvaluesinthechchannelattheendofthegoroutinerun,theywillbepassedalongtothenextstepinthesequence.So,topassoutmessage(s)fromthefinalgoroutine,put(..)themintoch.Asshown,toavoidtheblockingofthosefinalput(..)s,weswitchchintobufferingmodebysettingitsbuffer_sizeto2(default:0).

Note:Seemanymoreexamplesofusingasynquence-flavoredCSPhere(https://gist.github.com/getify/e0d04f1f5aa24b1947ae).

Promisesandgeneratorsprovidethefoundationalbuildingblocksuponwhichwecanbuildmuchmoresophisticatedand

Review

Page 170: You Don't Know JS: Async & Performance

capableasynchrony.

asynquencehasutilitiesforimplementingiterablesequences,reactivesequences(aka"Observables"),concurrentcoroutines,andevenCSPgoroutines.

Thosepatterns,combinedwiththecontinuation-callbackandPromisecapabilities,givesasynquenceapowerfulmixofdifferentasynchronousfunctionalities,allintegratedinonecleanasyncflowcontrolabstraction:thesequence.

Page 171: You Don't Know JS: Async & Performance

Ihavemanypeopletothankformakingthisbooktitleandtheoverallserieshappen.

First,ImustthankmywifeChristenSimpson,andmytwokidsEthanandEmily,forputtingupwithDadalwayspeckingawayatthecomputer.Evenwhennotwritingbooks,myobsessionwithJavaScriptgluesmyeyestothescreenfarmorethanitshould.ThattimeIborrowfrommyfamilyisthereasonthesebookscansodeeplyandcompletelyexplainJavaScripttoyou,thereader.Iowemyfamilyeverything.

I'dliketothankmyeditorsatO'Reilly,namelySimonSt.LaurentandBrianMacDonald,aswellastherestoftheeditorialandmarketingstaff.Theyarefantastictoworkwith,andhavebeenespeciallyaccommodatingduringthisexperimentinto"opensource"bookwriting,editing,andproduction.

Thankyoutothemanyfolkswhohaveparticipatedinmakingthisbookseriesbetterbyprovidingeditorialsuggestionsandcorrections,includingShelleyPowers,TimFerro,EvanBorden,ForrestL.Norvell,JenniferDavis,JesseHarlin,KrisKowal,RickWaldron,JordanHarband,BenjaminGruenbaum,VyacheslavEgorov,DavidNolen,andmanyothers.AbigthankyoutoJakeArchibaldforwritingtheForewordforthistitle.

Thankyoutothecountlessfolksinthecommunity,includingmembersoftheTC39committee,whohavesharedsomuchknowledgewiththerestofus,andespeciallytoleratedmyincessantquestionsandexplorationswithpatienceanddetail.John-DavidDalton,Juriy"kangax"Zaytsev,MathiasBynens,AxelRauschmayer,NicholasZakas,AngusCroll,ReginaldBraithwaite,DaveHerman,BrendanEich,AllenWirfs-Brock,BradleyMeck,DomenicDenicola,DavidWalsh,TimDisney,PetervanderZee,AndreaGiammarchi,KitCambridge,EricElliott,andsomanyothers,Ican'tevenscratchthesurface.

TheYouDon'tKnowJSbookserieswasbornonKickstarter,soIalsowishtothankallmy(nearly)500generousbackers,withoutwhomthisbookseriescouldnothavehappened:

JanSzpila,nokiko,MuraliKrishnamoorthy,RyanJoy,CraigPatchett,pdqtrader,DaleFukami,rayhatfield,R0drigoPerez[Mx],DanPetitt,JackFranklin,AndrewBerry,BrianGrinstead,RobSutherland,SergiMeseguer,PhillipGourley,MarkWatson,JeffCarouth,AlfredoSumaran,MartinSachse,MarcioBarrios,Dan,AimelyneM,MattSullivan,DelnattePierre-Antoine,JakeSmith,EugenTudorancea,Iris,DavidTrinh,simonstl,RayDaly,UrosGruber,JustinMyers,ShaiZonis,Mom&Dad,DevinClark,DennisPalmer,BrianPanahiJohnson,JoshMarshall,Marshall,DennisKerr,MattSteele,ErikSlagter,Sacah,JustinRainbow,ChristianNilsson,Delapouite,D.Pereira,NicolasHoizey,GeorgeV.Reilly,DanReeves,BrunoLaturner,ChadJennings,ShaneKing,JeremiahLeeCohick,od3n,StanYamane,MarkoVucinic,JimB,StephenCollins,ÆgirÞorsteinsson,EricPederson,Owain,NathanSmith,Jeanetteurphy,AlexandreELISÉ,ChrisPeterson,RikWatson,LukeMatthews,JustinLowery,MortenNielsen,VernonKesner,ChetanShenoy,PaulTregoing,MarcGrabanski,DionAlmaer,AndrewSullivan,KeithElsass,TomBurke,BrianAshenfelter,DavidStuart,KarlSwedberg,Graeme,BrandonHays,JohnChristopher,Gior,manojreddy,ChadSmith,JaredHarbour,MinoruTODA,ChrisWigley,DanielMee,Mike,Handyface,AlexJahraus,CarlFurrow,RobFoulkrod,MaxShishkin,LeighPennyJr.,RobertFerguson,MikevanHoenselaar,HasseSchougaard,rajanvenkataguru,JeffAdams,TraeRobbins,RolfLangenhuijzen,JorgeAntunes,AlexKoloskov,HughGreenish,TimJones,JoseOchoa,MichaelBrennan-White,NagaHarishMuvva,BarkócziDávid,KittHodsden,PaulMcGraw,SaschaGoldhofer,AndrewMetcalf,MarkusKrogh,MichaelMathews,MattJared,Juanfran,GeorgieKirschner,KennyLee,TedZhang,AmitPahwa,InbalSinai,DanRaine,SchabseLaks,MichaelTervoort,AlexandreAbreu,AlanJosephWilliams,NicolasD,CindyWong,RegBraithwaite,LocalPCGuy,JonFriskics,ChrisMerriman,JohnPena,JacobKatz,SueLockwood,MagnusJohansson,JeremyCrapsey,GrzegorzPawłowski,niconuzzaci,ChristineWilks,HansBergren,charlesmontgomery,Arielבר-לבבFogel,IvanKolev,DanielCampos,HughWood,ChristianBradford,FrédéricHarper,IonuţDanPopa,JeffTrimble,RupertWood,TreyCarrico,PanchoLopez,Joëlkuijten,TomAMarra,JeffJewiss,JacobRios,PaoloDiStefano,SoledadPenades,ChrisGerber,AndreyDolganov,WilMooreIII,ThomasMartineau,Kareem,BenThouret,UdiNir,MorganLaupies,jorycarson-burson,NathanLSmith,EricDamonWalters,DerryLozano-Hoyland,GeoffreyWiseman,mkeehner,KatieK,ScottMacFarlane,BrianLaShomb,AdrienMas,christopherross,IanLittman,DanAtkinson,ElliotJobe,NickDozier,PeterWooley,John

YouDon'tKnowJS:Async&Performance

AppendixC:Acknowledgments

Page 172: You Don't Know JS: Async & Performance

Hoover,dan,MartinA.Jackson,HéctorFernandoHurtado,andyennamorato,PaulSeltmann,MelissaGore,Dave

Pollard,JackSmith,PhilipDaSilva,GuyIsraeli,@megalithic,DamianCrawford,FelixGliesche,AprilCarterGrant,Heidi,jimtierney,AndreaGiammarchi,NicoVignola,DonJones,ChrisHartjes,AlexHowes,johngibbon,DavidJ.Groom,BBox,Yu'Dilys'Sun,NateSteiner,BrandonSatrom,BrianWyant,WesleyHales,IanPouncey,TimothyKevinOxley,GeorgeTerezakis,sanjayraj,JordanHarband,MarkoMcLion,WolfgangKaufmann,PascalPeuckert,DaveNugent,MarkusLiebelt,WellingGuzman,NickCooley,DanielMesquita,RobertSyvarth,ChrisCoyier,RémyBach,AdamDougal,AlistairDuggin,DavidLoidolt,EdRicher,BrianChenault,GoldFireStudios,CarlesAndrés,CarlosCabo,YuyaSaito,robertoricardo,BarnettKlane,MikeMoore,KevinMarx,JustinLove,JoeTaylor,PaulDijou,MichaelKohler,RobCassie,MikeTierney,CodyLeroyLindley,tofuji,ShimonSchwartz,Raymond,LucDeBrouwer,DavidHayes,RhysBrett-Bowen,Dmitry,AzizKhoury,Dean,ScottTolinski-LevelUp,ClementBoirie,DjordjeLukic,AntonKotenko,RafaelCorral,PhilipHurwitz,JonathanPidgeon,JasonCampbell,JosephC.,SwiftOne,JanHohner,DerickBailey,getify,DanielCousineau,ChrisCharlton,EricTurner,DavidTurner,JoëlGaleran,DharmaVagabond,adam,DirkvanBergen,dave♥♫★furf,VedranZakanj,RyanMcAllen,NataliePatriceTucker,EricJ.Bivona,AdamSpooner,AaronCavano,KellyPacker,EricJ,MartinDrenovac,Emilis,MichaelPelikan,ScottF.Walter,JoshFreeman,BrandonHudgeons,vijaychennupati,BillGlennon,RobinR.,TroyForster,otaku_coder,Brad,Scott,FrederickOstrander,AdamBrill,SebFlippence,MichaelAnderson,Jacob,AdamRandlett,Standard,JoshuaClanton,SebastianKouba,ChrisDeck,SwordFire,HannesPapenberg,RichardWoeber,hnzz,RobCrowther,JedidiahBroadbent,SergeyChernyshev,Jay-ArJamon,BenCombee,lucianobonachela,MarkTomlinson,KitCambridge,MichaelMelgares,JacobAdams,AdrianBruinhout,BevWieber,ScottPuleo,ThomasHerzog,AprilLeone,DanielMizieliński,KeesvanGinkel,JonAbrams,ErwinHeiser,AviLaviad,Davidnewell,Jean-FrancoisTurcot,NikoRoberts,ErikDana,CharlesNeill,AaronHolmes,GrzegorzZiółkowski,NathanYoungman,Timothy,JacobMather,MichaelAllan,MohitSeth,RyanEwing,BenjaminVanTreese,MarceloSantos,DenisWolf,PhilKeys,ChrisYung,TimoTijhof,MartinLekvall,Agendine,GregWhitworth,HelenHumphrey,DougalCampbell,JohannesHarth,BrunoGirin,BrianHough,DarrenNewton,CraigMcPheat,OlivierTille,DennisRoethig,MathiasBynens,BrendanStromberger,sundeep,JohnMeyer,RonMale,JohnFCrostonIII,gigante,CarlBergenhem,B.J.May,RebekahTyler,TedFoxberry,JordanReese,TerrySuitor,afeliz,TomKiefer,DarraghDuffy,KevinVanderbeken,AndyPearson,SimonMacDonald,AbidDin,ChrisJoel,TomasTheunissen,DavidDick,PaulGrock,BrandonWood,JohnWeis,dgrebb,NickJenkins,ChuckLane,JohnnyMegahan,marzsman,TatuTamminen,GeoffreyKnauth,AlexanderTarmolov,JeremyTymes,ChadAuld,SeanParmelee,RobStaenke,DanBender,Yannickderwa,JoshuaJones,GeertPlaisier,TomLeZotte,ChristenSimpson,StefanBruvik,JustinFalcone,CarlosSantana,MichaelWeiss,PabloVilloslada,PeterdeHaan,DimitrisIliopoulos,seyDoggy,AdamJordens,NoahKantrowitz,AmolM,MatthewWinnard,DirkGinader,PhinamBui,DavidRapson,AndrewBaxter,FlorianBougel,MichaelGeorge,AlbanEscalier,DanielSellers,SashaRudan,JohnGreen,RobertKowalski,DavidI.Teixeira(@ditma,CharlesCarpenter,JustinYost,SamS,DenisCiccale,KevinSheurs,YannickCroissant,PauFracés,StephenMcGowan,ShawnSearcy,ChrisRuppel,KevinLamping,JessicaCampbell,ChristopherSchmitt,Sablons,JonathanReisdorf,BunniGek,TeddyHuff,MichaelMullany,MichaelFürstenberg,CarlHenderson,RickYoesting,ScottNichols,HernánCiudad,AndrewMaier,MikeStapp,JesseShawl,SérgioLopes,jsulak,ShawnPrice,JoelClermont,ChrisRidmann,SeanTimm,JasonFinch,AidenMontgomery,ElijahManor,DerekGathright,JesseHarlin,DillonCurry,CourtneyMyers,DiegoCadenas,ArnedeBree,JoãoPauloDubas,JamesTaylor,PhilippKraeutli,MihaiPăun,SamGharegozlou,joshjs,MattMurchison,EricWindham,TimoBehrmann,AndrewHall,joshuaprice,ThéophileVillard

Thisbookseriesisbeingproducedinanopensourcefashion,includingeditingandproduction.WeoweGitHubadebtofgratitudeformakingthatsortofthingpossibleforthecommunity!

ThankyouagaintoallthecountlessfolksIdidn'tnamebutwhoInonethelessowethanks.Maythisbookseriesbe"owned"byallofusandservetocontributetoincreasingawarenessandunderstandingoftheJavaScriptlanguage,tothebenefitofallcurrentandfuturecommunitycontributors.