Концепция - Дистилляция, как работает обещание?

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

Если бы мне пришлось угадать, это просто функция, которая запускается при срабатывании обратного вызова.

Может ли кто-то реализовать самое основное обещание в нескольких строках кода без связи.

Например, из этого answer

Фрагмент 1

var a1 = getPromiseForAjaxResult(ressource1url);
a1.then(function(res) {
    append(res);
    return a2;
});

Как функция, переданная then, знает, когда ее запускать.

То есть, как он возвращается обратно к коду обратного вызова, который запускает ajax при завершении.

Фрагмент 2

// generic ajax call with configuration information and callback function
ajax(config_info, function() {
    // ajax completed, callback is firing.
});

Как связаны эти два фрагмента?

Guess:

// how to implement this

(function () {
    var publik = {};
        _private;
    publik.then = function(func){
        _private = func;
    };
    publik.getPromise = function(func){
        // ??
    };
    // ??
}())

Ответы

Ответ 1

Может ли кто-нибудь реализовать самое основное обещание в нескольких строках?

Вот он:

function Promise(fn) {
    // takes a function as an argument that gets the fullfiller
    var callbacks = [], result;
    fn(function fulfill() {
        if (result) return;
        result = arguments;
        for (var c;c=callbacks.shift();)
            c.apply(null, arguments);
    });
    this.addCallback = function(c) {
        if (result)
            c.apply(null, result)
        else
            callbacks.push(c);
    }
}

Дополнительно then с цепочкой (для которой вам понадобится ответ):

Promise.prototype.then = function(fn) {
    var that = this;
    return new Promise(function(c){
        that.addCallback(function() {
            var result = fn.apply(null, arguments);
            if (result instanceof Promise)
                result.addCallback(c);
            else
                c(result);
        });
    });
};

Как связаны эти два фрагмента?

ajax вызывается из функции getPromiseForAjaxResult:

function getPromiseForAjaxResult(ressource) {
    return new Promise(function(callback) {
        ajax({url:ressource}, callback);
    });
}

Ответ 2

По сути, обещание - это просто объект, у которого есть флаг, указывающий, было ли оно выполнено, и список функций, которые он поддерживает для уведомления, если/когда оно выполнено. Иногда код может сказать больше, чем слова, поэтому здесь приведен очень простой, нереальный пример, который просто предназначен для передачи концепций:

// See notes following the code for why this isn't real-world code
function Promise() {
    this.settled = false;
    this.settledValue = null;
    this.callbacks = [];
}
Promise.prototype.then = function(f) {
    if (this.settled) {
        f(this.settledValue);                // See notes 1 and 2
    } else {
        this.callbacks.push(f);
    }
                                             // See note 3 about 'then'
                                             // needing a return value
};
Promise.prototype.settle = function(value) { // See notes 4 and 5
    var callback;

    if (!this.settled) {
        this.settled = true;
        this.settledValue = value;
        while (this.callbacks.length) {
            callback = this.callbacks.pop();
            callback(this.settledValue);      // See notes 1 and 2
        }
    }
};

Таким образом, Promise содержит состояние и функции для вызова, когда обещание выполнено. Процесс выполнения обещания обычно является внешним по отношению к самому объекту Promise (хотя, конечно, это зависит от фактического использования, вы можете комбинировать их - например, как с объектами jQuery ajax [ jqXHR ]).

Опять же, вышесказанное является чисто концептуальным и в нем отсутствуют некоторые важные вещи, которые должны присутствовать в любой реальной реализации обещаний, чтобы это было полезно:

  1. then и settle должны всегда вызывать обратный вызов асинхронно, даже если обещание уже решен. then следует, потому что в противном случае вызывающая сторона не знает, будет ли обратный вызов асинхронным. settle должны, потому что обратные вызовы не должны работать до тех пор, после того, как settle вернулся. (Обещания ES2015 делают обе эти вещи. JQuery Deferred не делает.)

  2. then и settle должно гарантировать, что сбой в обратном вызове (например, исключение) не распространяется непосредственно на код, вызывающий then или не settle. Это частично связано с № 1 выше, и в особенности с № 3 ниже.

  3. then должен вернуть новое обещание, основанное на результате обратного вызова (тогда или позже). Это довольно важно для составления обещанных операций, но значительно усложнило бы вышесказанное. Любая разумная реализация обещаний делает.

  4. Нам нужны разные типы операций "урегулирования": "разрешить" (базовое действие выполнено успешно) и "отклонить" (не удалось). В некоторых случаях использования может быть больше состояний, но решаемые и отклоненные являются основными двумя. (Обещания ES2015 имеют разрешение и отклоняются.)

  5. Мы можем каким-то образом сделать settle (или отдельное resolve и reject) частным, так что только создатель обещания может выполнить его. (Обещания ES2015 - и некоторые другие - делают это, заставляя конструктор Promise принимать обратный вызов, который получает resolve и reject качестве значений параметров, поэтому только код в этом обратном вызове может разрешить или отклонить [если код в обратном вызове не делает их общедоступными каким-либо образом].)

И т.д.

Ответ 3

Здесь реализована легкая реализация обещаний, называемая "последовательность", которую я использую в своей повседневной работе:

(function() {
    sequence = (function() {
        var chained = [];
        var value;
        var error;

        var chain = function(func) {
            chained.push(func);
            return this;
        };

        var execute = function(index) {
            var callback;
            index = typeof index === "number" ? index : 0;

            if ( index >= chained.length ) {
                chained = [];
                return true;
            }

            callback = chained[index];

            callback({
                resolve: function(_value) {
                    value = _value;
                    execute(++index);
                },
                reject: function(_error) {
                    error = _error;
                    execute(++index);
                },
                response: {
                    value: value,
                    error: error
                }
            });
        };

        return {
            chain: chain,
            execute: execute
        };
    })();
})();

После инициализации вы можете использовать последовательность следующим образом:

sequence()
    .chain(function(seq) {
        setTimeout(function() {
            console.log("func A");
            seq.resolve();
        }, 2000);
    })
    .chain(function(seq) {
        setTimeout(function() {
            console.log("func B");
        }, 1000)
    })
    .execute()

Чтобы включить фактическое цепочку, вам нужно вызвать функцию resolve() объекта seq, которую ваши обратные вызовы должны использовать в качестве аргумента.

Последовательность раскрывает два общедоступных метода:

  • chain - этот метод просто подталкивает ваши обратные вызовы к частному массиву
  • execute - этот метод использует рекурсию, чтобы обеспечить правильное последовательное выполнение ваших обратных вызовов. Он в основном выполняет ваши обратные вызовы в том порядке, в котором вы их скопировали, передав объект seq каждому из них. Как только текущий обратный вызов будет разрешен/отклонен, выполняется следующий обратный вызов.

Метод "выполнить" - это то, где происходит волшебство. Он передает объект "seq" во все ваши обратные вызовы. Поэтому, когда вы вызываете seq.resolve() или seq.reject(), вы фактически вызываете следующий цепной обратный вызов.

Обратите внимание, что эта реализация сохраняет ответ только от ранее выполненного обратного вызова.

Дополнительные примеры и документацию см. в: https://github.com/nevendyulgerov/sequence

Ответ 4

Вот простая реализация Promise, которая работает для меня.

    function Promise(callback) {
        this._pending = [];
        this.PENDING = "pending";
        this.RESOLVED = "resolved";
        this.REJECTED = "rejected";
        this.PromiseState = this.PENDING;
        this._catch = function (error) {
            console.error(error);
        };
        setTimeout(function () {
            try {
                callback.call(this, this.resolve.bind(this), this.reject.bind(this));
            } catch (error) {
                this.reject(error);
            }
        }.bind(this), 0)
    };
    Promise.prototype.resolve = function (object) {
        if (this.PromiseState !== this.PENDING) return;
        while (this._pending.length > 0) {
            var callbacks = this._pending.shift();
            try {
                var resolve = callbacks.resolve;
                if (resolve instanceof Promise) {
                    resolve._pending = resolve._pending.concat(this._pending);
                    resolve._catch = this._catch;
                    resolve.resolve(object);
                    return resolve;
                }
                object = resolve.call(this, object);
                if (object instanceof Promise) {
                    object._pending = object._pending.concat(this._pending);
                    object._catch = this._catch;
                    return object;
                }
            } catch (error) {
                (callbacks.reject || this._catch).call(this, error);
                return;
            }
        }
        this.PromiseState = this.RESOLVED;
        return object;
    };
    Promise.prototype.reject = function (error) {
        if (this.PromiseState !== this.PENDING) return;
        this.PromiseState = this.REJECTED;
        try {
            this._catch(error);
        } catch (e) {
            console.error(error, e);
        }
    };
    Promise.prototype.then = function (onFulfilled, onRejected) {
        onFulfilled = onFulfilled || function (result) {
            return result;
        };
        this._catch = onRejected || this._catch;
        this._pending.push({resolve: onFulfilled, reject: onRejected});
        return this;
    };
    Promise.prototype.catch = function (onRejected) {
        // var onFulfilled = function (result) {
        //     return result;
        // };
        this._catch = onRejected || this._catch;
        // this._pending.push({resolve: onFulfilled, reject: onRejected});
        return this;
    };
    Promise.all = function (array) {
        return new Promise(function () {
            var self = this;
            var counter = 0;
            var finishResult = [];

            function success(item, index) {
                counter++;
                finishResult[index] = item;
                if (counter >= array.length) {
                    self.resolve(finishResult);
                }
            }
            for(var i in array) {
                var item = array[i];
                if (item instanceof Promise) {
                    item.then(function (result) {
                        success(result,this);
                    }.bind(i), function (error) {
                        array.map(function (item) {
                            item.PromiseState = Promise.REJECTED
                        });
                        self._catch(error);
                    })
                } else {
                    success(item, i);
                }
            }
        });
    };
    Promise.race = function (array) {
        return new Promise(function () {
            var self = this;
            var counter = 0;
            var finishResult = [];
            array.map(function (item) {
                if (item instanceof Promise) {
                    item.then(function (result) {
                        array.map(function (item) {
                            item.PromiseState = Promise.REJECTED
                        });
                        self.resolve(result);
                    }, function (error) {
                        array.map(function (item) {
                            item.PromiseState = Promise.REJECTED
                        });
                        self._catch(error);
                    })
                } else {
                    array.map(function (item) {
                        item.PromiseState = Promise.REJECTED
                    });
                    self.resolve(item);
                }
            })
        });
    };
    Promise.resolve = function (value) {
        return new Promise(function (resolve, reject) {
            try {
                resolve(value);
            } catch (error) {
                reject(error);
            }
        });
    };
    Promise.reject = function (error) {
        return new Promise(function (resolve, reject) {
            reject(error);
        });
    }

Обсуждаем здесь. Скрипт: здесь.

Ответ 5

вот абсолютный минимум обещанной архитектуры

function Promise(F) {
  var gotoNext = false;
  var stack = [];
  var args = [];

  var isFunction = function(f) {
    return f && {}.toString.call(f) === '[object Function]';
  };

  var getArguments = function(self, _args) {
    var SLICE = Array.prototype.slice;

    _args = SLICE.call(_args);
    _args.push(self);

    return _args;
  };

  var callNext = function() {
    var method = stack.shift();

    gotoNext = false;
    if (isFunction(method)) method.apply(null, args);
  };

  var resolve = [(function loop() {
    if (stack.length) setTimeout(loop, 0);
    if (gotoNext) callNext();
  })];

  this.return = function() {
    gotoNext = true;
    args = getArguments(this, arguments);
    if(resolve.length) resolve.shift()();

    return this;
  };

  this.then = function(fn) {
    if (isFunction(fn)) stack.push(fn);

    return this;
  };

  return this.then(F).return();
}


// --- below is a working implementation --- //

var bar = function(p) {
  setTimeout(function() {
    console.log("1");
    p.return(2);
  }, 1000);
};

var foo = function(num, p) {
  setTimeout(function() {
    console.log(num);
    p.return(++num);
  }, 1000);
};

new Promise(bar)
  .then(foo)
  .then(foo)
  .then(foo);