Самая простая реализация promises (обещаний) на JavaScript


В примере ниже в функцию doSomething передаются callback-функции для обработки результата либо ошибки, если результат не удалось получить.
doSomething(function(result) {
// success }, function(err) { // error });

Если нужно выполнить несколько функций последовательно, то в каждую функцию надо передавать callback-функцию, которая будет вызвана в конце функции. Пример:
doSomething1(function() {
doSomething2(function() { doSomething3(function() { }); }); });
В конце doSomething1 стоит вызов doSomething2, а в конце doSomething2 соотвественно doSomething3.

В следующем примере, все элементы массива обрабатываются асинхронной функцией. Непонятно как сделать что-то важное когда завершится последняя.
for (var i = 0; i < a.length; i++) {
doSomething(a[i], function() { ... }); }
Можно использовать рекурсию: для первого элемента вызвать doSomething, в коллбэке рекурсивно вызвать doSomething для следующего элемента, и удалить его из массива. Так до тех пор пока массив не окажется пустым. такие Примерно так же работают и Promises. Они позволяют записывать асинхронные функции в удобном последовательном виде.


Асинхронность в JavaScript-приложениях – обычное дело. Любой обмен данными – асинхронный, что HTTP, что чтение файла, что БД. Все просто, если запрос один – callback, и все дела. Если логика сложнее, то приложение в худшем случае превращается в «Callback Pyramid of Doom» или обрастает разной магией. Promise – это подход, который выпрямляет вложенные запросы, превращает «асинхронную лапшу» в структурированный код и делает ваше приложение лучше. 


Пример promises на JavaScript:
promise.then(function() {
doSomething1(); }).then(function() { doSomething2(); }).then(function() { doSomething3(); });
Каждый вызов then добавляет функцию в очередь функций запланированных на исполнение.

В JavaScript есть всего лишь один способ выполнить функцию отложено — setTimeout с нулевым интервалом.

Код для реализации promises:
function chain(callback) {
var queue = []; function _next() { var cb = queue.shift(); if (cb) { cb(_next); } } setTimeout(_next, 0); var then = function(cb) { queue.push(cb); return { then: then } } return then(callback); }
Сокращенная версия (140byt.es):
//137 bytes
function chain(a){function c(){var a=b.shift();a&&a(c)}var b=[];setTimeout(c,0);var d=function(a){return b.push(a),{then:d}};return d(a)}
queue - очередь запланированных функций.
_next - рекурсивная функция.

Пример использования:
chain(function(next) {
console.log('1'); setTimeout(function() { console.log('2'); next(); }, 1000); }).then(function(next) { console.log('3'); setTimeout(function() { console.log('4'); next(); }, 1000); }).then(function(next) { console.log('5'); next(); });

  1. В функцию chain передается первая callback-функция. В свою очередь callback-функция тоже имеет аргументом callback-функцию next, которую надо вызвать по окончании работы первой callback-функции.
  2. В функции chain создается место под очередь запланированных функций.
  3. В функции chain происходит асинхронный вызов функции _next.
  4. В функции chain происходит вызов функции then, которая добавляет первую callback-функцию в очередь и возвращает в функцию chain объект с собой.
  5. Функция chain возвращает объект с функцией then.
  6. В функцию then передается вторая callback-функция.
  7. Вторая callback-функция добавляется в очередь и снова возвращается объект с функцией then.
  8. Аналогично добавляется третья callback-функция.
  9. Начинает работать функция _next. Не забывайте что JS однопоточный язык, поэтому до выполнения функции _next браузер добрался только после того как выполнил основной код и оказался ни чем не занятым поэтому и приступил к выполнению функции _next.

Вот пример с более подробным логом, который помогает понять как браузер выполняет этот код:
function chain(callback) {
    console.log("<chain>");
    var queue = [];
    function _next() {
        console.log("<_next>");
        var cb = queue.shift();
        if (cb) {
            cb(_next);
        }
        console.log("</_next>")
    }
    setTimeout(_next, 0);
    var then = function(cb) {
        console.log("<then>");
        queue.push(cb);
        console.log("</then>");
        return { then: then }
    }
    console.log("</chain>");
    return then(callback);
}

chain(function(next) {
    console.log('1');
    setTimeout(function() {
        console.log('2');
        next();
    }, 1000);
}).then(function(next) {
    console.log('3');
    setTimeout(function() {
        console.log('4');
        next();
    }, 1000);
}).then(function(next) {
    console.log('5');
    next();
});


Вывод:
<chain> 
</chain> 
<then> 
</then> 
<then>
</then> 
<then>
</then> 
<_next> 
1 
</_next> 
2 
<_next> 
3 
</_next> 
4 
<_next> 
5
<_next> 
</_next> 
</_next> 


--