Стара приказка говорить про те, що ви повинні вибрати правильний інструмент для роботи. ES6 обіцянки забезпечують основи. Якщо все, що вам колись хочеться чи потрібно, - це основи, то це повинно / могло б працювати для вас просто чудово. Але в інструментарі є більше інструментів, ніж просто основи, і є ситуації, коли ці додаткові інструменти дуже корисні. І я б заперечував, що в ES6-обіцянках навіть відсутні деякі основи на зразок обізнаності, які є корисними майже в кожному проекті node.js.
Я найбільше знайомий із бібліотекою обіцянок Bluebird, тому я буду говорити здебільшого зі свого досвіду роботи з цією бібліотекою.
Отже, ось мої головні 6 причин використовувати більш спроможну бібліотеку Promise
Необіцяні інтерфейси асинхронізації - .promisify()
і .promisifyAll()
неймовірно корисні для обробки всіх тих асинхронних інтерфейсів, які все ще потребують простого зворотного виклику та ще не повертають обіцянок - один рядок коду створює обіцяну версію всього інтерфейсу.
Швидше - Bluebird набагато швидше, ніж рідні обіцянки в більшості середовищ.
Послідовність ітерації масиву асинхронізації - Promise.mapSeries()
або Promise.reduce()
дозволяють вам повторювати масив, викликаючи операцію асинхронізації на кожному елементі, але послідовність операцій асинхронізації, щоб вони відбувалися одна за одною, а не всі одночасно. Це можна зробити або тому, що цього вимагає цільовий сервер, або тому, що вам потрібно передати один результат наступному.
Polyfill - якщо ви хочете використовувати обіцянки у старих версіях клієнтів браузера, вам все одно знадобиться поліфайл. Також може отримати здатний поліфіл. Оскільки node.js обіцяє ES6, вам не потрібна полізаповнення в node.js, але ви можете в браузері. Якщо ви кодуєте і сервер node.js, так і клієнта, може бути дуже корисним мати однакову бібліотеку обіцянок і функції в обох (простіше ділитися кодом, перемикатися в контексті між середовищами, використовувати загальні методи кодування для коду асинхронізації тощо). .).
Інші корисні функції - Bluebird має Promise.map()
, Promise.some()
, Promise.any()
, Promise.filter()
, Promise.each()
і Promise.props()
всі з яких іноді зручно. Хоча ці операції можна виконувати за допомогою обіцянок ES6 та додаткового коду, Bluebird поставляється з цими операціями вже заздалегідь складеними та попередньо протестованими, так що простіше і менше коду для їх використання.
Вбудовані попередження та сліди повного стека - Bluebird має ряд вбудованих попереджень, які попереджають вас про проблеми, які, ймовірно, неправильний код чи помилка. Наприклад, якщо ви викликаєте функцію, яка створює нову обіцянку всередині .then()
обробника, не повертаючи цю обіцянку (щоб з'єднати її в поточний ланцюжок обіцянок), то в більшості випадків це випадкова помилка, і Bluebird попередить вас про це ефект. Інші вбудовані попередження Bluebird описані тут .
Ось докладніше про ці різні теми:
PromisifyAll
У будь-якому проекті node.js я негайно використовую Bluebird скрізь, оскільки я .promisifyAll()
дуже багато використовую у стандартних node.js модулях, таких як fs
модуль.
Node.js сам по собі не забезпечує перспективний інтерфейс для вбудованих модулів, які не асинхронізують IO, як fs
модуль. Отже, якщо ви хочете використовувати обіцянки з тими інтерфейсами, вам залишається або кодувати рукою обгортку обіцянок навколо кожної функції, яку ви використовуєте, або отримати бібліотеку, яка може зробити це для вас або не використовувати обіцянки.
Bluebird Promise.promisify()
і Promise.promisifyAll()
забезпечують автоматичне обгортання node.js, що викликає API асинхронних конвенцій для повернення обіцянок. Це надзвичайно корисно та економить час. Я ним користуюся постійно.
Ось приклад того, як це працює:
const Promise = require('bluebird');
const fs = Promise.promisifyAll(require('fs'));
fs.readFileAsync('somefile.text').then(function(data) {
// do something with data here
});
Альтернативою було б вручну створити власну обгортку обіцянок для кожного fs
API, який ви хочете використовувати:
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
});
І це потрібно робити вручну для кожної функції API, яку ви хочете використовувати. Це явно не має сенсу. Це код котла. Ви також можете отримати утиліту, яка робить це за вас. Bluebird's Promise.promisify()
і Promise.promisifyAll()
є такою утилітою.
Інші корисні функції
Ось деякі функції Bluebird, які я конкретно вважаю корисними (наведено кілька прикладів коду нижче, як вони можуть зберегти код або розвинути швидкість):
Promise.promisify()
Promise.promisifyAll()
Promise.map()
Promise.reduce()
Promise.mapSeries()
Promise.delay()
Окрім корисної функції, Promise.map()
також підтримує параметр одночасності, який дозволяє вам вказати, скільки операцій слід дозволити одночасно виконувати, що особливо корисно, коли у вас є багато чого робити, але не можна перевантажувати деякі зовні ресурс.
Деякі з них можна назвати автономними та використовувати їх за обіцянкою, яка сама вирішує ітерабельний режим, що може заощадити багато коду.
Поліфіл
У проекті веб-переглядача, як правило, ви все ще хочете підтримувати деякі веб-переглядачі, які не підтримують Promise, ви все одно потребуєте полізаповнення. Якщо ви також використовуєте jQuery, іноді ви можете просто використовувати підтримку обіцянок, вбудовану в jQuery (хоча вона дещо болісно нестандартна, можливо, зафіксована в jQuery 3.0), але якщо проект передбачає будь-яку значущу активність асинхронізації, я вважаю розширені функції в Bluebird дуже корисні.
Швидше
Також варто зазначити, що обіцянки Bluebird виявляються значно швидшими, ніж обіцянки, вбудовані у V8. Дивіться цей пост, щоб отримати більше обговорень на цю тему.
Велика річ Node.js відсутня
Що б змусити мене використовувати Bluebird менше в розробці node.js, було б, якби node.js вбудований у функцію промотивації, щоб ви могли зробити щось подібне:
const fs = requirep('fs');
fs.readFileAsync('somefile.text').then(function(data) {
// do something with data here
});
Або просто пропонуйте вже обіцяні методи як частину вбудованих модулів.
До цього часу я роблю це з Bluebird:
const Promise = require('bluebird');
const fs = Promise.promisifyAll(require('fs'));
fs.readFileAsync('somefile.text').then(function(data) {
// do something with data here
});
Здається, трохи дивним є підтримка ES6 для обіцянок, вбудована в node.js, і жоден із вбудованих модулів не повертає обіцянок. Це потрібно розібратися в node.js. До цього часу я використовую Bluebird для розмежування цілих бібліотек. Таким чином, здається, що обіцянки зараз приблизно на 20% реалізовані в node.js, оскільки жоден із вбудованих модулів не дозволяє вам використовувати обіцянки, не спочатку вручну запускаючи їх.
Приклади
Ось приклад прості обіцянки та розцінки Bluebird та паралельне Promise.map()
читання набору файлів та повідомлення про завершення з усіма даними:
Прості обіцянки
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
});
Синій птах Promise.map()
іPromise.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
});
Ось приклад прості обіцянки проти Bluebird, які підкреслюються, і Promise.map()
читаючи купу URL-адрес з віддаленого хоста, де ви можете прочитати не більше 4 одночасно, але хочете зберегти стільки запитів паралельно, скільки дозволено:
Прості обіцянки 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();
});
}
Синій птах обіцяє
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
});