Промисы просты в использовании, когда вы уже поняли принцип. Однако существуют некоторые подводные камни, которые доставят немало неприятностей.
Перевод/переработка статьи Promise Anti-patterns.
Вложенные промисы (Nested Promises)
В коде может возникнуть ситуация целой кучи вложенных друг в друга промисов:
[javascript]
loadSomething().then(function(something) {
loadAnotherthing().then(function(another) {
DoSomethingOnThem(something, another);
});
});
[/javascript]
Причина, по которой было сделано так, – вам нужно было сделать что-то с результатами обоих промисов, а поставить их в цепочку было нельзя, так как then() возвращает только результат последнего.
Но на самом деле причина в том, что вы не знаете о методе all().
Так правильно:
[javascript]
q.all([loadSomething(), loadAnotherThing()])
.spread(function(something, another) {
DoSomethingOnThem(something, another);
});
[/javascript]
Намного проще, правда? Метод q.all() обработает переданные в него промисы и будет возвращен результат посредством метода spread()
Разорваная цепочка (The Broken Chain)
Ваш код выглядит следующим образом:
[javascript]
function anAsyncCall() {
var promise = doSomethingAsync();
promise.then(function() {
somethingComplicated();
});
return promise;
}
[/javascript]
Проблема: любая ошибка в методе somethingComplicated() не будет отловлена. Идея промисов – выполнение в цепочке: каждый вызов then() возвращает новый промис. То есть в данном примере вместо первоначального промиса, нужно возвращать последний промис, который вернет then().
Так правильно:
[javascript]
function anAsyncCall() {
var promise = doSomethingAsync();
return promise.then(function() {
somethingComplicated()
});
}
[/javascript]
Беспорядок в коллекции (The Collection Kerfuffle)
Имеется массив, стоит задача последовательно асинхронно обработать каждый элемент. У вас получится что-то наподобие рекурсии:
[javascript]
function workMyCollection(arr) {
var resultArr = [];
function _recursive(idx) {
if (idx >= resultArr.length) return resultArr;
return doSomethingAsync(arr[idx]).then(function(res) {
resultArr.push(res);
return _recursive(idx + 1);
});
}
return _recursive(0);
}[/javascript]
Ух, это код совсем не понятен с первого взгляда.
Когда вы изначально не знаете сколько звеньев будет в цепочке промисов, то крайне полезны станут методы map() и reduce() объекта Array.
Помните, что q.all() принимает массив промисов и разрешает его в массив результатов? Мы можем просто применить map() в комбинации с q.all():
[javascript]
function workMyCollection(arr) {
return q.all(arr.map(function(item) {
return doSomethingAsync(item);
}));
}[/javascript]
К сожалению, это не будет являться полным решением, так как такое решение убивает всю асинхронность вызовов и займет соответственно больше времени.
Если вам хочется запустить ваши промисы одновременно, то можно сделать reduce.
Так лучше:
[javascript]
function workMyCollection(arr) {
return arr.reduce(function(promise, item) {
return promise.then(function(result) {
return doSomethingAsyncWithResult(item, result);
});
}, q());
}[/javascript]
Вероятно тоже не идеальное решение, но уж точно лучше изначального.
Тень промиса (The Ghost Promise)
Иногда возникают ситуации, когда метод в зависимости от условия выполняет что-то синхронно либо асинхронно, но для поддержания единства поведения всего метода вы создаете промис и для синхронной операции:
[javascript]
var promise;
if (asyncCallNeeded) promise = doSomethingAsync();
else promise = Q.resolve(42);
promise.then(function() {
doSomethingCool();
});[/javascript]
Это конечно не самый худший антипаттерн, но мы определенно можем сделать его лучше обернув значение/промис в Q(). Этот метод возьмет либо значение либо промис и обработает его соответственно:
[javascript]
Q(asyncCallNeeded ? doSomethingAsync() : 42)
.then( function(value){
doSomethingGood(); })
.catch( function(err) {
handleTheError();
});[/javascript]
Пристрастный отлов ошибок (The Overly Keen Error Handler)
Метод then() принимает 2 параметра-обработчика: успешного вызова и вызова с ошибкой. Вероятно где-то у вас встретится такой код:
[javascript]
somethingAsync.then( function() {
return somethingElseAsync();
}, function(err) {
handleMyError(err);
});
[/javascript]
Проблема заключается в том, что если ошибка случится в первом обработчике, то она не будет направлена на обработчик ошибок.
Так правильно:
[javascript]
somethingAsync .then(function() {
return somethingElseAsync();
}) .then(null, function(err) {
handleMyError(err);
});
[/javascript]
или с использованием catch():
[javascript]
somethingAsync .then(function() {
return somethingElseAsync();
}) .catch(function(err) {
handleMyError(err);
});[/javascript]
Забытый промис (The Forgotten Promise)
Ситуация: вы вызываете метод, который возвращает промис, но забывая об этом создаете еще один:
[javascript]
var deferred = Q.defer();
doSomethingAsync().then(function(res) {
res = manipulateMeInSomeWay(res);
deferred.resolve(res);
}, function(err) {
deferred.reject(err);
});
return deferred.promise;
[/javascript]
Это ломает изначальную задумку, ради которой промисы и создавались.
Так правильно:
[javascript]
return doSomethingAsync().then(function(res) {
return manipulateMeInSomeWay(res);
});
[/javascript]