【问题标题】:Concept - Distilling how a promise works?概念 - 提炼承诺如何运作?
【发布时间】:2020-09-19 03:16:47
【问题描述】:

我查看了许多实现,它们看起来都非常不同,我无法真正提炼出 Promise 的本质。

如果我不得不猜测它只是一个在回调触发时运行的函数。

有人可以在几行代码中实现最基本的承诺吗?

例如来自这个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.
});

这两个 sn-ps 有什么关系?

猜测:

// how to implement this

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

【问题讨论】:

标签: javascript


【解决方案1】:

从根本上说,promise 只是一个对象,它有一个标志,表明它是否已解决,以及它维护的函数列表,用于通知是否/何时解决。代码有时比文字更能说明问题,所以这里有一个非常基本的、非现实世界的示例,纯粹是为了帮助传达概念:

// 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 完成时要调用的函数。 解决 承诺的行为通常在 Promise 对象本身之外(当然,这取决于实际使用,您可以将它们结合起来 - 例如,与 jQuery 的 ajax [ jqXHR] 个对象)。

再次重申,以上内容纯粹是概念性的,并且遗漏了一些重要的东西,这些东西必须存在于任何现实世界的 Promise 实现中才能发挥作用:

  1. thensettle 应该总是异步调用回调,即使 promise 已经完成。 then 应该是因为否则调用者不知道回调是否是异步的。 settle 应该是因为回调不应该在 settle 返回之后运行。 (ES2015 的 Promise 做到了这两件事。jQuery 的 Deferred 没有。)

  2. thensettle 应确保回调中的失败(例如异常)不会直接传播到调用 thensettle 的代码。这与上面的#1 部分相关,与下面的#3 更相关。

  3. then 应该根据调用回调的结果(当时或以后)返回一个 new 承诺。这对于组合 promise-ified 操作是相当基础的,但会使上述内容明显复杂化。任何合理的 Promise 实现都可以。

  4. 我们需要不同类型的“解决”操作:“解决”(基础操作成功)和“拒绝”(失败)。一些用例可能有更多的状态,但解决和拒绝是基本的两个。 (ES2015的promise有resolve和reject。)

  5. 我们可能会以某种方式将settle(或单独的resolvereject)设为私有,这样只有promise 的创建者才能解决它。 (ES2015 承诺 - 和其他几个 - 通过让 Promise 构造函数接受接收 resolvereject 作为参数值的回调来做到这一点,因此只有该回调中的代码可以解析或拒绝 [除非回调中的代码使它们以某种方式公开]。)

等等等等

【讨论】:

  • @livingston_mechanical 做出承诺的代码在完成时也会调用它的fulfill 方法。
  • @livingston_mechanical: 不管是什么都能兑现承诺;通常(正如 Waleed 上面所说的),创造承诺的东西。这是关于 Promise 的一大优点:它们将 Promise 接口与 Promise 生成器的细节分离。
  • 最佳报价:f(this) 识字编程?
  • @livingston_mechanical 如果您谈论的是库的 AJAX 函数(如 jQuery),它已经构建为使用 Promise。如果您创建自己的 AJAX 函数,那么您要做的是在 AJAX 函数内部创建一个承诺对象,并将该承诺返回给外部(用于链接或保存到变量),然后您将提供 @987654345 @ 和 .onerror 引用 .fulfill.lapse 或任何你的“信守承诺”和“违背承诺”。
  • @livingston_mechanical: “那么在 ajax 调用的示例中,ajax 调用将如何访问 Promise 对象以调用执行?” 你收到了 Promise 来自 ajax 调用,因此它只保留一个副本,以便可以对其调用fulfill 方法。
【解决方案2】:

有人可以用几行代码实现最基本的承诺吗?

这里是:

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

带有链接的附加 thenthe answer 需要):

Promise.prototype.then = function(fn) {
    return new Promise(fulfill => {
        this.addCallback((...args) => {
            const result = fn(...args);
            if (result instanceof Promise)
                result.addCallback(fulfill);
            else
                fulfill(result);
        });
    });
};

这两个 sn-ps 有什么关系?

ajaxgetPromiseForAjaxResult 函数调用:

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

【讨论】:

  • 两种实现方式截然不同。
  • ……但是它们的原理是一样的。有什么不明白的地方,代码还是挺简单的?
  • Promises 与 Events/PubSub 有两个很大的不同:一个 Promise 只代表一个值(将来可能会到达),并且您可以随时添加回调 - 它们甚至会以该值执行如果它已经到了。您可能还想阅读en.wikipedia.org/wiki/Futures_and_promises
  • 我认为 Promise 是一个类,而不是一个函数,并且您通过“new Promise”或通过调用执行“new Promise”的函数创建了一个 Promise。我是对还是错?
  • @DavidSpector 是的,这是一个类,你不能省略new。但是请注意,构造函数也可以用function 定义,并且这个答案是在ES6 class 语法和原生Promises 成为标准之前编写的。上面的实现演示了概念,它不是一个polyfill(例如,缺少完整的错误处理)。
【解决方案3】:

我已经在 ES7 中实现了一个。使用链接,它是 70 行,如果那算得少的话。我认为状态机是实现承诺的正确范例。生成的代码比许多ifs 恕我直言更容易理解。在this article 中有完整描述。

代码如下:

const states = {
    pending: 'Pending',
    resolved: 'Resolved',
    rejected: 'Rejected'
};

class Nancy {
    constructor(executor) {
        const tryCall = callback => Nancy.try(() => callback(this.value));
        const laterCalls = [];
        const callLater = getMember => callback => new Nancy(resolve => laterCalls.push(() => resolve(getMember()(callback))));
        const members = {
            [states.resolved]: {
                state: states.resolved,
                then: tryCall,
                catch: _ => this
            },
            [states.rejected]: {
                state: states.rejected,
                then: _ => this,
                catch: tryCall
            },
            [states.pending]: {
                state: states.pending,
                then: callLater(() => this.then),
                catch: callLater(() => this.catch)
            }
        };
        const changeState = state => Object.assign(this, members[state]);
        const apply = (value, state) => {
            if (this.state === states.pending) {
                this.value = value;
                changeState(state);
                for (const laterCall of laterCalls) {
                    laterCall();
                }
            }
        };

        const getCallback = state => value => {
            if (value instanceof Nancy && state === states.resolved) {
                value.then(value => apply(value, states.resolved));
                value.catch(value => apply(value, states.rejected));
            } else {
                apply(value, state);
            }
        };

        const resolve = getCallback(states.resolved);
        const reject = getCallback(states.rejected);
        changeState(states.pending);
        try {
            executor(resolve, reject);
        } catch (error) {
            reject(error);
        }
    }

    static resolve(value) {
        return new Nancy(resolve => resolve(value));
    }

    static reject(value) {
        return new Nancy((_, reject) => reject(value));
    }

    static try(callback) {
        return new Nancy(resolve => resolve(callback()));
    }
}

【讨论】:

    【解决方案4】:

    这是一个轻量级的 Promise 实现,称为“序列”,我在日常工作中使用它:

    (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()
    

    要启用实际的链接,您需要调用 seq 对象的 resolve() 函数,您的回调必须将其用作参数。

    Sequence 公开了两个公共方法:

    • chain - 此方法只是将您的回调推送到私有数组
    • execute - 此方法使用递归来启用回调的正确顺序执行。它基本上按照您通过将 seq 对象传递给每个回调的顺序来执行您的回调。当前回调解决/拒绝后,将执行下一个回调。

    “执行”方法是神奇发生的地方。它将“seq”对象传递给所有回调。因此,当您调用 seq.resolve() 或 seq.reject() 时,您实际上会调用下一个链式回调。

    请注意,此实现仅存储来自先前执行的回调的响应。

    更多示例和文档,请参考: https://github.com/nevendyulgerov/sequence

    【讨论】:

    • 请解释一下这比 Promise 更好。我没看到。
    • @DavidSpector 这不是一个承诺,缺乏所有的核心特征。这只是一个带有链接语法的惰性回调序列。
    【解决方案5】:

    这是一个适合我的简单 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);
            });
        }
    

    讨论here。 小提琴:here.

    【讨论】:

    • “讨论”链接指向代码,而不是讨论。这个实现比标准的 Promise 好多少?
    • 这不符合 Promises/A+ 规范的核心要求,即 .then() 返回一个具有单独状态的新 Promise 实例。它还只支持一个拒绝处理程序。
    【解决方案6】:

    这是承诺架构的绝对最小值

    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);

    【讨论】:

      猜你喜欢
      • 2019-05-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-06-15
      • 1970-01-01
      • 1970-01-01
      • 2012-08-04
      相关资源
      最近更新 更多