I mit tidligere indlæg kiggede jeg på hvordan man kunne udføre asynkrone kald ved hjælp af promises. Nu er tiden kommet til at vælge hvilket bibliotek, der passer på det næste projekt.
Der er rigtig mange varianter og udbredelsen er stor. En søgning på promise via node-pakke-manageren npmjs.org gav 1150 biblioteker der enten tilbyder eller er afhængige af promises. Heraf har jeg valgt at kigge på 12 forskellige biblioteker, alle er open source og alle tilbyder en promise-lignende struktur.
Opdateringer:
- 2014/03/06 – Rettet nogle stavefejl (@rauschma via Twitter)
- 2014/03/07 – Fjernet de rå størrelser, da de ikke gav meget mening (@x-skeww via Reddit)
- 2014/03/07 – Tilføjet at catiline underliggende bruger lie. (@CWMma via Twitter)
- 2014/03/07 – Præciseret hvad performance-testen gør. (@CWMma via Twitter)
API’et på tværs af de mange biblioteker er næsten ens, så derfor har jeg valgt at kigge på:
Features
Hvilke overordnede promise-relaterede features indeholder hvert bibliotek?
Størrelse
Her tænker jeg mest på browseren – hvor mange ekstra bytes bliver mit website tilført?
Hastighed
Hvor hurtige er de basale promise-operationer i biblioteket? Man må forvente at disse vil blive kørt mange gange, og derfor har det en betydning.
Bibliotekerne
Først en oversigt over de udvalgte kandidater, deres licens og forfatter. Bemærk at navnet linker til bibliotekets kilde (typisk Github).
Licens | Forfatter | Bemærkning | |
Bluebird | MIT | Petka Antonov | Fyldt med features og efter sigende en af markedets hurtigste og der er lagt speciel vægt på nem fejlsøgning vha. gode stacktraces. Features kan slåes til og fra via tilpassede builds. |
Catiline | MIT | Calvin Metcalf | Mere designet til håndtering af web workers men indeholder en promise implementation. Bruger underliggende lie. |
ES6 Promise polyfill | MIT | Jake Archibald | Låner kode fra RSVP, men implementeret så den overholder specifikationen for ECMAScript 6. |
jQuery | MIT | The jQuery Foundation | Klassisk bibliotek til DOM-manipulation på tværs af browsere |
kew | Apache 2.0 | The Obvious Corporation | Gætter på at det skal udtales ‘Q’, kan betragtes som en optimeret udgave af Q med et mindre featuresæt |
lie | MIT | Calvin Metcalf | |
MyDeferred | MIT | RubaXa | Lille implementation i Gist format |
MyPromise | MIT | [email protected] | Lille implementation i Gist format |
Q | MIT | Kris Kowal | Meget kendt implementation, en light-udgave af denne er at finde i det populære framework fra Google, AngularJS |
RSVP | MIT | Tilde | |
when | MIT | cujoJS | |
Yui | BSD | Yahoo! | Yahoo’s bibliotek til DOM-manipulation på tværs af browsere |
Features
Nedenstående er et kig på hvilke overordnede features hvert enkelt bibliotek tilbyder. Der bliver kun kigget på de features, der er direkte relaterede til promises:
Promises/A+ | Progression | Delayed promise | Parallel synchronization | Web Workers | Cancellation | Generators | Wrap jQuery | |
Bluebird | ✓ | ✓ (+389 B) | ✓ (+615 B) | ✓ (+272 B) | – | ✓ (+396 B) | ✓ (+276 B) | ✓ |
Catiline | ✓ | – | – | ✓ | ✓ | – | – | – |
ES6 Promise polyfill | ✓ | – | – | ✓ | – | – | – | – |
JQuery | – | ✓ | – | ✓ | – | – | – | ✓ |
kew | ✓ | – | ✓ | ✓ | – | – | – | – |
lie | ✓ | – | – | – | – | – | – | – |
MyDeferred | ✓ | – | – | ✓ | – | – | – | – |
MyPromise | – | – | ✓ | ✓ | – | – | – | – |
Q | ✓ | ✓ | ✓ | ✓ | – | – | ✓ | ✓ |
RSVP | ✓ | – | – | ✓ | – | – | ✓ | – |
when | ✓ | ✓ | ✓ | ✓ | – | ✓ | ✓ | ✓ |
Yui | ✓ | ✓ | – | ✓ | – | – | – | – |
Tallene i parantes ved Bluebird er den øgede størrelse i bytes tilvalget af den enkelte feature giver.
Promises/A+
Overholdes Promises/A+ specifikationen?
Progression
Er der funktioner til løbende notifikation af status på en aynkron opgave før at den er endelig udført?
Delayed promise
Kan man skabe en forsinket promise der bliver opfyldt efter et specificeret stykke tid?
Parallel synchronization
Er der funktioner til synkronisering af flere operationer, altså kan vi få en promise der bliver opfyldt når en række andre promises er opfyldt?
Web Workers
Kan den asynkrone kode blive afviklet via en web-worker – altså blive skubbet til en separat afviklingstråd?
Cancellation
Kan man stoppe en promise-afvikling, før den er færdig?
Generators
Understøttes de kommende funktioner omkring generators i JavaScript herunder yield?
Wrap jQuery
Kan promises produceret af jQuery blive konverteret til bibliotekets promises?
Størrelse
For de biblioteker der tilbyder tilpassede builds, er der valgt den mindste konfiguration der stadig tilbyder promises. Hvert enkelt bibliotek er blevet kørt igennem Googles Closure compiler. Alle er blevet kørt på ‘Simple’, for at undgå evt. beskadende ændringer. Det tal der kommer ud er inklusiv komprimering i http-stakken, så der er altså tale om de rå antal bytes som man må forvente at ens applikation vokser med ved tilvalg af hvert enkelt bibliotek:
Hastighed
Hastigheden er blevet målt vha. sitet jsPerf der giver mulighed for at køre de samme tests på mange forskellige browsere og platforme, inklusiv mobil og tablets. Testen skaber en ny promise med hvert enkelt framework og der bliver så målt på hvor stor forsinkelse der er i at afvikle den asynkrone del (Se flere detailjer her). Bemærk at det ikke er en test der er lavet af mig men en masse fantastiske frivillige (vi er på version 91):
Konklusion
Over halvdelen af verdens websites bruger allerede jQuery. Har man arbejdet lidt med de promises jQuery tilbyder, finder man hurtigt ud af, at de er uhensigtsmæssige. Jeg har selv oplevet problemer med fejlende kode, der ikke som man forventer, afviser promisen, når der opstår fejl, men hvor fejlen alligevel bobler op og i sidste ende bliver en global browserfejl. Promise-specifikationen dikterer at fejl skal fanges og promises afvises, så her er man sikret.
Derfor, hvis man i dag har et site baseret på jQuery er det nærliggende at vælge et af de biblioteker, der tilbyder konvertering fra jQuery’s usikre promises til den mere sikre slags. Hvis størrelsen har en stor betydning, man slæber jo allerede rundt på jQuery, er enten Q eller when begge gode bud, med mange features og rimelig hastighed.
Er man ikke så bekymret for størrelsen, er Bluebird et bedre valg. Den modulære opbygning gør det nemt at vælge features til og fra og så har den en betydelig test suite der tester performance af meget andet end det ene aspekt der er dækket i det her indlæg.
Hvis performance er alt afgørende, er kew et godt bud. Det er et team der har taget Q og kigget på hvordan de kunne få forbruget af ressourcer ned – det har altså resulteret i et begrænset, men meget hurtigt bibliotek.
Går man efter en mere begrænset løsning, med god hastighed og uden store biblioteker er ES6 Promise polyfill et godt valg – dermed kan man, på sigt når browserne udvikler sig, fjerne biblioteket helt, og blot bruge de indbyggede funktioner.
This post is also available in English at Complexitymaze.com
Vælger ikke at nævne noget som helst om at “wuuut, hvordan kan du vælge promises over callbacks wut wut??” 🙂
I stedet vil jeg anbefale at du prøver http://node-modules.com til at lede efter moduler. Den bruger en ret avanceret ranking-algoritme og hvis du vil kan du signe ind med github hvor den så bruger de moduler du har starred eller programmører du følger.
Har arbejdet med bluebird de sidste par dage og må sige at det er ret fantastisk – det har ændret min kodestil fuldstændigt. Fejlhåndteringen alene er fantastisk. Se iøvrigt forrige indlæg.
Callbacks er efter min mening en dårlig løsning, da jeg så skal bære dem med i alle kald m.v. fx.
SaveUser(user, failCallback, successCallback, alwaysCallback, progressCallback);
Hellere:
SaveUser(user) : Promise;
Fint site til node moduler.
Well, det er jo naermest en religion blandt folk, men jeg vil sige at hvis du modtager flere callbacks saa goer du ofte noget forkert. Det kan enten ske fordi din funktion/modul pludselig tager for meget ansvar eller fordi det i virkeligheden er en event-emitter.
Jeg synes for det meste at node.js udviklere godt kan finde ud af dem paa en ordentlig maade saa det ser jeg ikke som et probem.
Noget jeg selv gaar og bakser med er at vaenne mig til at bruge streams til alt. Det er virkelig der node spiller max, men det er noget af en tilvaenning.
Ja det var overdrevet for at fremme forståelsen. Har nu svært ved at se at en løsning kan blive lige så elegant som med promises, når man også tænker på fejlhåndtering m.v.
Jeg kan godt lide bluebirds måde at indkapsle en hel funktion i en promise, så sørger den for fejlhåndering, lange stack traces m.v. Og så er der hele synkroniseringshistorien med flere callbacks – det bliver bare imo. mere elegant med promises.
Events har da også deres rolle, men giver tit mere arbejde med livscyklus m.v.
Ja, reaction streams er for fede, og også lidt svære at venne sig til – det er sådan lidt en process hvor man ender med at kunne fjerne en masse kode 🙂
Ja det var overdrevet for at fremme forståelsen. Har nu svært ved at se at en løsning kan blive lige så elegant som med promises, når man også tænker på fejlhåndtering m.v.
Jeg kan godt lide bluebirds måde at indkapsle en hel funktion i en promise, så sørger den for fejlhåndering, lange stack traces m.v. Og så er der hele synkroniseringshistorien med flere callbacks – det bliver bare imo. mere elegant med promises.
Events har da også deres rolle, men giver tit mere arbejde med livscyklus m.v.
Ja, reaction streams er for fede, og også lidt svære at venne sig til – det er sådan lidt en process hvor man ender med at kunne fjerne en masse kode 🙂
Sådan af interesse, hvorfor ville man vælge callbacks frem for at bruge promises ?
Fra mit synspunkt, så er promises med til at gøre det hele mere læsbart og vedligeholdelses venligt.
Jeg har lavet applikationer i angular det sidste års tid, og i har i den forbindelse brugt angulars promise bibliotek q. Det er jeg meget begejstret for 🙂
På trods af at q er en lidt minimeret udgave af Q.
Godt spørgsmål.
Sådan lidt tænkt, så kan man næsten få en GOTO-agtig opførsel med en lang callback-stak. Hvor man med promises mere får noget der ligner exception-handling i callback forløbet.
Ja, det får mig nu ikke til at vælge callback’s frem for promises.
Det var egentlig også et spm til @Tobias’s første svar, det lader til han vil ?
Sådan af interesse, hvorfor ville man vælge callbacks frem for at bruge promises ?
Fra mit synspunkt, så er promises med til at gøre det hele mere læsbart og vedligeholdelses venligt.
Jeg har lavet applikationer i angular det sidste års tid, og i har i den forbindelse brugt angulars promise bibliotek q. Det er jeg meget begejstret for 🙂
På trods af at q er en lidt minimeret udgave af Q.
Godt spørgsmål.
Sådan lidt tænkt, så kan man næsten få en GOTO-agtig opførsel med en lang callback-stak. Hvor man med promises mere får noget der ligner exception-handling i callback forløbet.
[…] som jeg skrev om tidligere krævede enten et separat bibliotek eller framework-understøttelse men er nu flyttet ind i […]