Есть ли еще причины использовать библиотеки обещаний, такие как Q или BlueBird, теперь у нас есть ES6 promises?

После Node.js добавлена ​​встроенная поддержка для promises, есть ли причины использовать библиотеки типа Q или BlueBird?

Например, если вы начинаете новый проект и можете предположить, что в этом проекте у вас нет каких-либо зависимостей, которые используют эти библиотеки, можем ли мы сказать, что больше нет причин использовать такие библиотеки?

Ответ 1

Старая пословица гласит, что вы должны выбрать правильный инструмент для работы. ES6 promises обеспечивают основы. Если все, что вам когда-либо понадобится или нужно, это основы, то это должно/может отлично работать для вас. Но в инструменте больше инструментов, чем просто основы, и есть ситуации, когда эти дополнительные инструменты очень полезны. И я бы сказал, что ES6 promises даже не хватает некоторых основ, таких как promisification, которые полезны почти в каждом проекте node.js.

Я больше всего знаком с библиотекой обещаний Bluebird, поэтому я буду говорить в основном из своего опыта с этой библиотекой.

Итак, вот мои лучшие 6 причин использовать более эффективную библиотеку Promise

  • Непролонгированные асинхронные интерфейсы - .promisify() и .promisifyAll() невероятно полезны для обработки всех этих асинхронных интерфейсов, которые по-прежнему требуют простых обратных вызовов и еще не возвращают promises - одна строка кода создает перспективную версию целого интерфейса.

  • Быстрее. Bluebird значительно быстрее, чем встроенный promises в большинстве сред.

  • Последовательность итераций асинхронного массива - Promise.mapSeries() или Promise.reduce() позволяет выполнять итерацию по массиву, вызывая операцию async для каждого элемента, но последовательность операций async, чтобы они произошли один за другим, не все в одно и то же время. Вы можете сделать это либо потому, что это требует сервер назначения, либо потому, что вам нужно передать один результат в следующий.

  • Polyfill. Если вы хотите использовать promises в более старых версиях браузеров, вам все равно понадобится polyfill. Может также получить способный polyfill. Поскольку node.js имеет ES6 promises, вам не нужен polyfill в node.js, но вы можете в браузере. Если вы кодируете сервер и клиент node.js, может быть очень полезно иметь одну и ту же библиотеку обещаний и функции в обоих (проще обмениваться кодами, переключать контекст между средами, использовать общие методы кодирования для асинхронного кода и т.д....).

  • Другие полезные функции. Bluebird имеет Promise.map(), Promise.some(), Promise.any(), Promise.filter(), Promise.each() и Promise.props() все из которых иногда удобны. Хотя эти операции могут выполняться с помощью ES6 promises и дополнительного кода, Bluebird поставляется с уже подготовленными и предварительно протестированными операциями, поэтому для их использования проще и меньше кода.

  • Встроенные предупреждения и трассировки полного стека. Bluebird имеет ряд встроенных предупреждений, предупреждающих вас о проблемах, которые, вероятно, являются неправильным кодом или ошибкой. Например, если вы вызываете функцию, которая создает новое обещание внутри обработчика .then(), не возвращая этого обещания (чтобы связать его с текущей цепочкой обещаний), то в большинстве случаев это случайная ошибка, и Bluebird даст вам предупреждая об этом. Другие встроенные предупреждения Bluebird описаны здесь.

Вот более подробная информация по этим различным темам:

PromisifyAll

В любом проекте node.js я сразу использую Bluebird везде, потому что я использую .promisifyAll() много для стандартных модулей node.js, таких как модуль fs.

Node.js сам по себе не обеспечивает интерфейс обещаний для встроенных модулей, которые делают async IO, как модуль fs. Таким образом, если вы хотите использовать promises с этими интерфейсами, вам остается либо скомпоновать оболочку обещания вокруг каждой функции модуля, которую вы используете, либо получить библиотеку, которая может сделать это для вас или не использовать promises.

Bluebird Promise.promisify() и Promise.promisifyAll() предоставляют автоматическую упаковку node.js API асинхронных вызовов для вызова promises. Это чрезвычайно полезно и экономит время. Я использую его все время.

Вот пример того, как это работает:

const Promise = require('bluebird');
const fs = Promise.promisifyAll(require('fs'));

fs.readFileAsync('somefile.text').then(function(data) {
   // do something with data here
});

Альтернативой было бы вручную создать свою собственную оболочку обещания для каждого API fs, который вы хотели использовать:

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 Promise.promisify() и Promise.promisifyAll() - такая утилита.

Другие полезные функции

Вот некоторые из функций Bluebird, которые я особенно нахожу полезными (здесь приведены несколько примеров кода, которые помогут сохранить код или скорость разработки):

Promise.promisify()
Promise.promisifyAll()
Promise.map()
Promise.reduce()
Promise.mapSeries()
Promise.delay()

В дополнение к своей полезной функции Promise.map() также поддерживает параметр concurrency, который позволяет указать, сколько операций должно быть разрешено работать одновременно, что особенно полезно, когда вам нужно что-то сделать, но не может подавить некоторый внешний ресурс.

Некоторые из них можно назвать автономными и использовать в обещании, которое само по себе разрешает итерабельному, которое может сэкономить много кода.


Polyfill

В проекте браузера, поскольку вы, как правило, хотите поддерживать некоторые браузеры, не поддерживающие Promise, в конечном итоге вам понадобится polyfill. Если вы также используете jQuery, иногда вы можете просто использовать поддержку обещаний, встроенную в jQuery (хотя в некоторых случаях это является болезненно нестандартным, возможно, исправлено в jQuery 3.0), но если проект включает какую-либо значимую асинхронную активность, я нахожу расширенные функции Bluebird очень полезны.


Быстрее

Также стоит отметить, что Bluebird promises выглядит значительно быстрее, чем promises, встроенный в V8. См. этот пост для более подробного обсуждения этой темы.


Большая вещь node.js отсутствует

Что бы я подумал о том, чтобы использовать Bluebird меньше в node.js, было бы, если node.js, встроенный в функцию promisify, чтобы вы могли сделать что-то вроде этого:

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 и ни один из встроенных модулей не возвращает promises. Это нужно отсортировать в node.js. До тех пор я использую Bluebird, чтобы обещать целые библиотеки. Таким образом, похоже, что promises составляет около 20%, реализованных в node.js, так как ни один из встроенных модулей не позволяет вам использовать promises без их ручного переноса в первую очередь.


Примеры

Здесь пример простой promises против Bluebird promisify и Promise.map() для чтения набора файлов параллельно и уведомления, когда все данные:

Обычная Promises

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() и 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
});

Здесь пример простого promises против Bluebird promisify и Promise.map() при чтении пула URL-адресов с удаленного хоста, где вы можете читать не более 4 за раз, но хотите, чтобы столько запросов параллельно, как допускается:

Обычный JS Promises

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
});