- PVSM.RU - https://www.pvsm.ru -
На Хабре уже встречались статьи о замечательной технологии Promises, которая в будущем станет частью стандарта ECMAScript 6, однако, в этих статьях не было подробного описания, почему же они так полезны и в чем таки их преимущества. Дабы заполнить этот пробел, я решил написать эту статью.
Внимание! Данная статья — не исчерпывающее руководство. Это скорее пиар хорошей и полезной технологии, замануха, показывающая позитивные стороны. Статья является компиляцией нескольких чужих статей, ссылки внизу.
Итак, возьмем кусок синхронного кода
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) { ... });
Реализация «Thennable» в jQuery немного отличается от стандартной — в стандарте аргумент в callback ровно один, в jQuery кол-во аргументов больше, что не мешает использовать объект, полученный из jQuery в нативной реализации. Как правило, больше, чем первый аргумент, ничего от jQuery и не нужно. Плюс существует конструкция:
var jsPromise = Promise.resolve($.ajax('/whatever.json'));
Автор: dfuse
Источник [8]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/javascript/74014
Ссылки в тексте:
[1] Revealing Constructor Pattern: https://blog.domenic.me/the-revealing-constructor-pattern
[2] http://thanpol.as/javascript/promises-a-performance-hits-you-should-be-aware-of: http://thanpol.as/javascript/promises-a-performance-hits-you-should-be-aware-of
[3] http://www.html5rocks.com/en/tutorials/es6/promises: http://www.html5rocks.com/en/tutorials/es6/promises
[4] https://github.com/tildeio/rsvp.js: https://github.com/tildeio/rsvp.js
[5] https://github.com/jakearchibald/es6-promise: https://github.com/jakearchibald/es6-promise
[6] http://www.2ality.com/2014/10/es6-promises-api.html: http://www.2ality.com/2014/10/es6-promises-api.html
[7] http://taoofcode.net/promise-anti-patterns: http://taoofcode.net/promise-anti-patterns
[8] Источник: http://habrahabr.ru/post/242767/
Нажмите здесь для печати.