promise – Stepan Suvorov Blog https://stepansuvorov.com/blog Release 2.0 Sun, 22 Jan 2017 08:50:33 +0000 en-US hourly 1 https://wordpress.org/?v=6.3.1 Angular + ES6 Promise https://stepansuvorov.com/blog/2016/09/angular-es6-promise/ https://stepansuvorov.com/blog/2016/09/angular-es6-promise/#comments Mon, 12 Sep 2016 17:43:29 +0000 http://stepansuvorov.com/blog/?p=3125 Continue reading ]]> It would be really nice if we could use native ECMAScript 2015 Promises with Angular instead of $q service that is provided from box to be close to pure JavaScript:

[javascript]
//somewhere inside component controller
let promise = new Promise((resolve) => setTimeout(() => resolve(‘resolved’), 2000));
promise.then(x => this.x = x);
[/javascript]

But in this case we will have to run digest manually for each resolve(to synchronise view and model):

[javascript]
let promise = new Promise((resolve) => setTimeout(() => resolve(‘resolved’), 2000));
promise.then(x => {
$scope.apply();
this.x = x;
});
[/javascript]

But what if we hack the Promise and intercept our digest call there:

[javascript]
class SubPromise extends Promise {
constructor(executor) {
super(function(_resolve, _reject) {
var resolve = (data) => {
var res = _resolve(data);
angular.element(document.body).injector().get(‘$rootScope’).$apply();
return res;
}
return executor(resolve, _reject);
});
}
}
[/javascript]

now we just need to overwrite standard Promise:

[javascript]
window.Promise = SubPromise;
[/javascript]

to keep it simple to cover for unit-tests you can also wrap it into an angular factory:

[javascript]
factory(‘Promise’, () => Promise);
[/javascript]

Here you can play with the code.

Discussion on stackoverflow about customising ES6 Promise.

!Attention: This experiment was made just for learning purposes and it should not be applied for the real projects.

]]>
https://stepansuvorov.com/blog/2016/09/angular-es6-promise/feed/ 2
setTimeout + ES6 Promise https://stepansuvorov.com/blog/2016/09/settimeout-es6-promise/ https://stepansuvorov.com/blog/2016/09/settimeout-es6-promise/#comments Sat, 10 Sep 2016 09:35:27 +0000 http://stepansuvorov.com/blog/?p=3120 Continue reading ]]> I’m just thinking how convenient could it be if we have setTimeout returning promise.

[javascript]
setTimeout(1000).then(/* … do whatever */);
[/javascript]

Let’s create our own one and call it ‘delay’ (using ES6 Promise):

[javascript]
delay = ms => new Promise(resolve => setTimeout(resolve, ms));
[/javascript]

so now we already can use our delay function:

[javascript]
delay(1000).then(/* … do whatever */);
[/javascript]

but let’s not forget about promise cancelation, in this case we need to store reject and timeout id:

[javascript]
delay = ms => {
var promiseCancel, promise = new Promise((resolve, reject) => {
let timeoutId = setTimeout(resolve, ms);
promiseCancel = () => {
clearTimeout(timeoutId);
reject(Error("Cancelled"));
}
});
promise.cancel = () => {
promiseCancel();
};
return promise;
}
[/javascript]

Plunker to play with this code.

Stackoverflow discussion.

]]>
https://stepansuvorov.com/blog/2016/09/settimeout-es6-promise/feed/ 4
Антипаттерны в промисах https://stepansuvorov.com/blog/2014/03/%d0%b0%d0%bd%d1%82%d0%b8%d0%bf%d0%b0%d1%82%d1%82%d0%b5%d1%80%d0%bd%d1%8b-%d0%b2-%d0%bf%d1%80%d0%be%d0%bc%d0%b8%d1%81%d0%b0%d1%85/ https://stepansuvorov.com/blog/2014/03/%d0%b0%d0%bd%d1%82%d0%b8%d0%bf%d0%b0%d1%82%d1%82%d0%b5%d1%80%d0%bd%d1%8b-%d0%b2-%d0%bf%d1%80%d0%be%d0%bc%d0%b8%d1%81%d0%b0%d1%85/#comments Thu, 13 Mar 2014 11:35:32 +0000 http://stepansuvorov.com/blog/?p=1587 Continue reading ]]>

Промисы просты в использовании, когда вы уже поняли принцип. Однако существуют некоторые подводные камни, которые доставят немало неприятностей.

Перевод/переработка статьи 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]

]]>
https://stepansuvorov.com/blog/2014/03/%d0%b0%d0%bd%d1%82%d0%b8%d0%bf%d0%b0%d1%82%d1%82%d0%b5%d1%80%d0%bd%d1%8b-%d0%b2-%d0%bf%d1%80%d0%be%d0%bc%d0%b8%d1%81%d0%b0%d1%85/feed/ 2