Il vecchio adagio dice che dovresti scegliere lo strumento giusto per il lavoro. Le promesse ES6 forniscono le basi. Se tutto ciò che desideri o hai bisogno sono le basi, allora dovrebbe / potrebbe funzionare bene per te. Ma ci sono più strumenti nel cestino degli strumenti oltre alle nozioni di base e ci sono situazioni in cui quegli strumenti aggiuntivi sono molto utili. E direi che le promesse di ES6 mancano anche di alcune nozioni di base come la promisificazione, utili in quasi tutti i progetti node.js.
Conosco molto bene la biblioteca di promesse Bluebird, quindi parlerò principalmente della mia esperienza con quella biblioteca.
Quindi, ecco i miei 6 principali motivi per utilizzare una libreria Promise più capace
Interfacce asincrone non promesse - .promisify()
e .promisifyAll()
sono incredibilmente utili per gestire tutte quelle interfacce asincrone che richiedono ancora semplici callback e non restituiscono ancora promesse - una riga di codice crea una versione promessa di un'intera interfaccia.
Più veloce - Bluebird è significativamente più veloce delle promesse native nella maggior parte degli ambienti.
Sequenziamento dell'iterazione asincrona dell'array - Promise.mapSeries()
oppure Promise.reduce()
consente di iterare attraverso un array, chiamando un'operazione asincrona su ciascun elemento, ma sequenziando le operazioni asincrone in modo che avvengano una dopo l'altra, non tutte contemporaneamente. È possibile farlo perché il server di destinazione lo richiede o perché è necessario passare un risultato al successivo.
Polyfill : se si desidera utilizzare le promesse nelle versioni precedenti dei client browser, sarà comunque necessario un polyfill. Può anche ottenere un polyfill capace. Poiché node.js ha promesse ES6, non è necessario un polyfill in node.js, ma è possibile che sia presente in un browser. Se stai codificando sia il server sia il client node.js, può essere molto utile avere la stessa libreria promessa e le stesse funzionalità in entrambi (più facile condividere il codice, cambiare contesto tra ambienti, usare tecniche di codifica comuni per codice asincrono, ecc. .).
Altre funzioni utili - Bluebird ha Promise.map()
, Promise.some()
, Promise.any()
, Promise.filter()
, Promise.each()
e Promise.props()
che sono tutti a portata di mano di tanto in tanto. Mentre queste operazioni possono essere eseguite con promesse ES6 e codice aggiuntivo, Bluebird viene fornito con queste operazioni già pre-costruite e pre-testate, quindi è più semplice e meno codice usarle.
Avvisi integrati e tracce di stack completo : Bluebird ha un numero di avvisi integrati che avvisano di problemi che probabilmente sono codice errato o un bug. Ad esempio, se chiami una funzione che crea una nuova promessa all'interno di un .then()
gestore senza restituire quella promessa (per collegarla all'attuale catena di promesse), nella maggior parte dei casi si tratta di un bug accidentale e Bluebird ti avviserà di ciò effetto. Altri avvisi Bluebird integrati sono descritti qui .
Ecco alcuni dettagli in più su questi vari argomenti:
PromisifyAll
In qualsiasi progetto node.js, uso immediatamente Bluebird ovunque perché uso .promisifyAll()
molto su moduli node.js standard come il fs
modulo.
Node.js non fornisce di per sé un'interfaccia promettente ai moduli integrati che eseguono l'asincronizzazione di IO come il fs
modulo. Pertanto, se si desidera utilizzare le promesse con quelle interfacce, è possibile codificare manualmente un wrapper di promessa attorno a ciascuna funzione del modulo utilizzata o ottenere una libreria che può fare ciò per sé o non utilizzare le promesse.
Bluebird's Promise.promisify()
ePromise.promisifyAll()
fornisce un wrapping automatico di node.js che chiama le API asincrone della convenzione per restituire le promesse. È estremamente utile e fa risparmiare tempo. Io lo uso per tutto il tempo.
Ecco un esempio di come funziona:
const Promise = require('bluebird');
const fs = Promise.promisifyAll(require('fs'));
fs.readFileAsync('somefile.text').then(function(data) {
// do something with data here
});
L'alternativa sarebbe quella di creare manualmente il proprio wrapper di promessa per ciascuna fs
API che si desidera utilizzare:
const fs = require('fs');
function readFileAsync(file, options) {
return new Promise(function(resolve, reject) {
fs.readFile(file, options, function(err, data) {
if (err) {
reject(err);
} else {
resolve(data);
}
});
});
}
readFileAsync('somefile.text').then(function(data) {
// do something with data here
});
E devi farlo manualmente per ogni funzione API che vuoi usare. Questo chiaramente non ha senso. È il codice boilerplate. Potresti anche ottenere un'utilità che fa questo lavoro per te. Bluebird's Promise.promisify()
ePromise.promisifyAll()
sono una tale utilità.
Altre caratteristiche utili
Ecco alcune delle funzionalità di Bluebird che trovo particolarmente utili (di seguito sono riportati alcuni esempi di codice su come questi possono salvare il codice o accelerare lo sviluppo):
Promise.promisify()
Promise.promisifyAll()
Promise.map()
Promise.reduce()
Promise.mapSeries()
Promise.delay()
Oltre alla sua utile funzione, Promise.map()
supporta anche un'opzione di concorrenza che ti consente di specificare quante operazioni devono essere eseguite contemporaneamente, il che è particolarmente utile quando hai molto da fare, ma non puoi sopraffarne alcune all'esterno risorsa.
Alcuni di questi possono essere entrambi definiti autonomi e utilizzati su una promessa che si risolve in un iterabile che può salvare molto codice.
polyfill
In un progetto browser, poiché in genere si desidera ancora supportare alcuni browser che non dispongono del supporto Promise, si finisce comunque per richiedere un polyfill. Se stai usando anche jQuery, a volte puoi semplicemente usare il supporto promessa integrato in jQuery (anche se è dolorosamente non standard in qualche modo, forse risolto in jQuery 3.0), ma se il progetto prevede attività asincrone significative, trovo le funzionalità estese di Bluebird sono molto utili.
Più veloce
Vale anche la pena notare che le promesse di Bluebird sembrano essere significativamente più veloci delle promesse integrate in V8. Vedi questo post per ulteriori discussioni su questo argomento.
Manca qualcosa di grosso Node.js
Ciò che mi farebbe pensare di usare Bluebird meno nello sviluppo di node.js sarebbe se node.js fosse incorporato in una funzione promisify in modo da poter fare qualcosa del genere:
const fs = requirep('fs');
fs.readFileAsync('somefile.text').then(function(data) {
// do something with data here
});
O semplicemente offri metodi già promessi come parte dei moduli integrati.
Fino ad allora, lo faccio con Bluebird:
const Promise = require('bluebird');
const fs = Promise.promisifyAll(require('fs'));
fs.readFileAsync('somefile.text').then(function(data) {
// do something with data here
});
Sembra un po 'strano avere il supporto delle promesse ES6 integrato in node.js e nessuno dei moduli integrati restituisce promesse. Questo deve essere risolto in node.js. Fino ad allora, uso Bluebird per promettere intere librerie. Quindi, sembra che le promesse siano state implementate in circa il 20% in node.js ora poiché nessuno dei moduli integrati ti consente di utilizzare le promesse senza prima inserirle manualmente.
Esempi
Ecco un esempio di promesse semplici rispetto a quelle promesse da Bluebird e Promise.map()
per la lettura di una serie di file in parallelo e la notifica al termine di tutti i dati:
Promesse semplici
const files = ["file1.txt", "fileA.txt", "fileB.txt"];
const fs = require('fs');
// make promise version of fs.readFile()
function fsReadFileP(file, options) {
return new Promise(function(resolve, reject) {
fs.readFile(file, options, function(err, data) {
if (err) return reject(err);
resolve(data);
});
});
}
Promise.all(files.map(fsReadFileP)).then(function(results) {
// files data in results Array
}, function(err) {
// error here
});
Bluebird Promise.map()
ePromise.promisifyAll()
const Promise = require('bluebird');
const fs = Promise.promisifyAll(require('fs'));
const files = ["file1.txt", "fileA.txt", "fileB.txt"];
Promise.map(files, fs.readFileAsync).then(function(results) {
// files data in results Array
}, function(err) {
// error here
});
Ecco un esempio di promesse semplici rispetto a quelle promesse da Bluebird e Promise.map()
quando si leggono un sacco di URL da un host remoto in cui è possibile leggere al massimo 4 alla volta, ma si desidera mantenere quante più richieste in parallelo consentite:
Promesse semplici di JS
const request = require('request');
const urls = [url1, url2, url3, url4, url5, ....];
// make promisified version of request.get()
function requestGetP(url) {
return new Promise(function(resolve, reject) {
request.get(url, function(err, data) {
if (err) return reject(err);
resolve(data);
});
});
}
function getURLs(urlArray, concurrentLimit) {
var numInFlight = 0;
var index = 0;
var results = new Array(urlArray.length);
return new Promise(function(resolve, reject) {
function next() {
// load more until concurrentLimit is reached or until we got to the last one
while (numInFlight < concurrentLimit && index < urlArray.length) {
(function(i) {
requestGetP(urlArray[index++]).then(function(data) {
--numInFlight;
results[i] = data;
next();
}, function(err) {
reject(err);
});
++numInFlight;
})(index);
}
// since we always call next() upon completion of a request, we can test here
// to see if there was nothing left to do or finish
if (numInFlight === 0 && index === urlArray.length) {
resolve(results);
}
}
next();
});
}
Bluebird Promises
const Promise = require('bluebird');
const request = Promise.promisifyAll(require('request'));
const urls = [url1, url2, url3, url4, url5, ....];
Promise.map(urls, request.getAsync, {concurrency: 4}).then(function(results) {
// urls fetched in order in results Array
}, function(err) {
// error here
});