Внимание! Данная статья — не исчерпывающее руководство. Это скорее пиар хорошей и полезной технологии, замануха, показывающая позитивные стороны. Статья является компиляцией нескольких чужих статей, ссылки внизу.
Итак, что же такое Promise?
- Promise это объект, используемый как заглушка для результата некоего отложенного (и возможно асинхронного) вычисления
- Способ писать последовательно/параллельно выполняемый асинхронный код как синхронный
- Часть стандарта ECMAScript 6
Важные основы
- Конструктор Promise — это имплементация паттерна Revealing Constructor Pattern
- «Статичные» методы
- Promise.resolve(val) and Promise.reject(val)
- Promise.all and Promise.race
- Концепция “Thenable” object
- В двух словах — это объект, обладающий методами .then() and .catch()
Состояния Promise
- Fulfilled — вычисление было успешным
- Rejected — ошибка при вычислении (любая)
- Pending — вычисление еще не завершилось (не fulfilled и не rejected)
- Settled — вычисление завершилось (не важно как)
Примеры
Итак, возьмем кусок синхронного кода
function foo() { var a = ‘a’; a = a + ‘b’; a = a + ‘c’; return a; }
И сделаем так, чтобы снаружи он выглядел как Promise:
function foo() { var a = ‘a’; a = a + ‘b’; a = a + ‘c’; return Promise.resolve(a); }
Следующий шаг — раздели каждый этап вычисления:
function foo() { return Promise.resolve(‘a’) .then(function(a){ return a + ‘b’; }) .then(function(a){ return a + ‘c’; }); }
Теперь каждый шаг можно сделать асинхронным, причем все выполнение будет по-прежнему последовательным.
Пойдем дальше, заменим один из шагов на «как бы асинхронную функцию, возвращающую Promise»:
function getB(a){ return Promise.resolve(a + ‘b’); } function foo() { return Promise.resolve(‘a’) .then(function(a){ return getB(a); }) .then(function(a){ return a + ‘c’; }); }
Встроенная функциональность допускает возврат в then(cb()) либо ошибки (throw new Error()) либо значения (return a+’c’;) либо следующего Promise.
Параллелизм
Представим, что сперва надо выполнить асинхронное действие 1, затем параллельно 2 и 3, и затем 4.
asyncAction1() .then(function(res1){ return Promise.all([async2(res1), async3(res1)]); }) .then(function(arr){ // an array of values var res2 = arr[0], res3 = arr[1]; return asyncAction4(res2, res3); }) .then(…);
Обработка ошибок
Самая замечательная вещь в Promises — это обработка ошибок. Не важно, на каком этапе и в какой глубине вложенности произошла ошибка, будь то reject или просто брошенное исключение, все это можно поймать и обработать, либо же прокинуть дальше.
asyncAction() .catch(function(rejection){ // пытаемся понять, можно ли как-то обработать ошибку if (rejection.code == ‘foo’) return ‘foo’; // никак нельзя, прокидываем ошибку дальше throw rejection; }) .then(…) .then(…) .catch(…);
Здесь нужно сделать замечание, что если, положим
var p1 = new Promise(...), p2 = new Promise(...) p3 = Promise.all([p1, p2]); p3.then(...).catch(...);
Catch будет ловить все, что пошло не так (причем не важно, что и как именно) в p1, p2, p3 и любых вложенных вызовах, что дико удобно. Обратная сторона — если catch() нет, то ошибка будет тихо проглочена. Однако библиотеки типа Q, как правило, имеют возможность задать обработчик непойманных ошибок, где их можно вывести в консоль или сделать что-то еще.
Слово об анти-паттернах
function anAsyncCall() { var promise = doSomethingAsync(); promise.then(function(){ somethingComplicated(); }); return promise; }
Иии легким движением руки мы потеряли второй Promise. Дело в том, что каждый вызов .then() или .catch() создает новый Promise, поэтому если создали новый, а вернули старый, то новый повиснет где-то в воздухе и никто не узнает, каков результат вычисления. Как бороться — просто вернуть новый Promise:
return promise.then(...);
Полезные ништяки
Задержка выполнения
function delay(ms){ return new Promise(function(resolve){ setTimeout(resolve, ms); } };
Пример использования
delay(5000).then(…);
Простейший таймаут
Поскольку Promise может быть settled только один раз (остальное игнорируется), то можно написать что-то вроде
function timeout(promise, ms) { return new Promise(function (resolve, reject) { promise.then(resolve); setTimeout(function () { reject(new Error('Timeout’)); }, ms); }); } timeout(asyncAction(), 5000).then(…).catch(…);
Кто первый встал — того и тапки.
Немного улучшенный таймаут
Чуть более очевидный пример таймаута через «статичную» функцию Promise.race().
Promise.race([ asynchronousAction(), delay(5000).then(function () { throw new Error('Timed out'); }) ]) .then(function (text) { ... }) .catch(function (reason) { ... });
Важное замечание об асинхронности
- Библиотека контролирует процесс выполнения, соответственно она заведует, как доставляется результат — синхронно или асинхронно
- В то же время, спецификация Promises/A+ требует, чтобы всегда использовался последний режим — асинхронный
- Таким образом, мы всегда можем полагаться на немедленное выполнение кода promise.then().catch() и т.д., и не заботиться, что какой-то их коллбеков съест все процессорное время (с оговорками, само собой)
Почему Promises лучше callbacks
- Они часть стандарта — умные люди думали и разрабатывали всякое для нашего удобства
- «Практически» не сказываются на производительности (http://thanpol.as/javascript/promises-a-performance-hits-you-should-be-aware-of)
- Попробуйте разрулить нетривиальный поток выполнения на callbacks, желательно с обработкой ошибок, если вы не напишете свою имплементацию Promises, код, скорее всего, будет невозможно читать и понимать
- Все Promises/A+ совместимые библиотеки могут принимать объекты друг друга (Angular прекрасно работает с объектами Q/Native Promise/RSVP и т.д.)
- А еще Promise — лучше, чем Deferred, потому что последний — это две концепции в одной, что безусловно плохо, а кроме этого, паттерн Revealing Constructor почти что гарантирует, что Promise будел settled только в рамках этого конструктора (ну или вы сам себе злобный Буратино)
Недостатки
- Не подходит для повторяющихся событий (правда, Promises не для того и писались)
- Не подходит для streams (аналогично)
- Текущая реализация в браузерах не позволяет следит за progress (собираются тоже включить в стандарт)
Замечание о jQuery
Реализация «Thennable» в jQuery немного отличается от стандартной — в стандарте аргумент в callback ровно один, в jQuery кол-во аргументов больше, что не мешает использовать объект, полученный из jQuery в нативной реализации. Как правило, больше, чем первый аргумент, ничего от jQuery и не нужно. Плюс существует конструкция:
var jsPromise = Promise.resolve($.ajax('/whatever.json'));
Ссылки для самостоятельного чтения
ссылка на оригинал статьи http://habrahabr.ru/post/242767/
Добавить комментарий