概念 - 提炼Promise如何运作?

IT技术 javascript
2021-01-12 10:47:47

我看过许多实现,它们看起来都如此不同,我无法真正提炼出 Promise 的本质。

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

有人能用几行代码实现最基本的Promise吗?

例如从这个答案

片段 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.
});

这两个片段有什么关系?

猜测:

// how to implement this

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

从根本上说,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的行为通常在Promise对象本身之外(当然,这取决于实际使用,您可以将它们组合起来——例如,与 jQuery 的ajax[ jqXHR] 对象一样)。

同样,上述内容纯粹是概念性的,并且遗漏了任何现实世界的 Promise 实现中必须存在的一些重要内容,才能使其有用:

  1. then并且settle应该始终异步调用回调,即使Promise已经解决。then应该是因为否则调用者不知道回调是否是异步的。settle应该是因为回调不应该在settle返回之后运行(ES2015 的 promise 会做这两件事。jQueryDeferred不会。)

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

  3. then应该根据调用回调的结果(当时或之后)返回一个新的Promise。这是构成Promise化操作的基础,但会使上述操作显着复杂化。任何合理的Promise实现都可以。

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

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

等等等等。

@livingston_mechanical:“那么在 ajax 调用的示例中,ajax 调用将如何访问 Promise 对象以调用实现?” 您会收到Promiseajax调用,因此它只是不断的副本,以便它可以调用fulfill方法就可以了。
2021-03-17 10:47:47
@livingston_mechanical:无论是什么实现了Promise;通常(正如 Waleed 上面所说的),创造Promise的东西。这是promise 的一大优点:它们将promise 接口与promise 生成器的细节分离。
2021-03-21 10:47:47
@livingston_mechanical 如果您在谈论库的 AJAX 函数(如 jQuery),它已经构建为使用 Promise。如果您创建自己的 AJAX 函数,那么您要做的是在 AJAX 函数内部创建一个Promise对象,并将该Promise返回给外部(用于链接或保存到变量),然后您将给出.onload.onerror引用到.fulfill.lapse或任何你“守信用”和“破发的Promise”被调用。
2021-03-27 10:47:47
@livingston_mechanical Promise的代码在完成后也会调用它的fulfill方法。
2021-03-29 10:47:47
最佳引语:f(this)文学编程?
2021-04-08 10:47:47

有人能用几行代码实现最基本的Promise吗?

这里是:

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

附加then链接(您需要回答):

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

这两个片段有什么关系?

ajaxgetPromiseForAjaxResult函数调用:

function getPromiseForAjaxResult(ressource) {
    return new Promise(function(callback) {
        ajax({url:ressource}, callback);
    });
}
……但它们的原理是一样的。有什么不明白的地方,代码很简单?
2021-03-15 10:47:47
Promises 与 Events/PubSub 有两个很大的不同:一个 promise 只代表一个值(它可能在未来到达),你可以随时添加回调——即使它已经到达,它们也会被执行。您可能还想阅读en.wikipedia.org/wiki/Futures_and_promises
2021-03-23 10:47:47
两种实现都大不相同。
2021-03-28 10:47:47
@DavidSpector 是的,这是一个类,您不能省略new. 但是请注意,构造函数也可以用 with 定义function,并且这个答案是在 ES6class语法和 nativePromise成为标准之前编写的上面的实现演示了这个概念,它不是一个 polyfill(例如,缺乏完整的错误处理)。
2021-04-06 10:47:47
我认为 Promise 是一个类,而不是一个函数,并且您通过“new Promise”或调用执行“new Promise”的函数来创建 Promise。我是对还是错?
2021-04-07 10:47:47

我已经在 ES7 中实现了一个。使用链接,它是 70 行,如果这算得上很少的话。我认为状态机是实现Promise的正确范例。结果代码比很多if恕我直言更容易理解这篇文章中进行了充分的描述

这是代码:

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

这是一个轻量级的 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 公开了两个公共方法:

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

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

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

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

请解释这比 Promise 更好的地方。我没有看到。
2021-03-23 10:47:47
@DavidSpector 这不是Promise,缺乏所有核心特征。它只是一个带有链接语法的惰性回调序列。
2021-04-08 10:47:47

这是一个对我有用的简单 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);
        });
    }

在这里讨论小提琴:这里

“讨论”链接指向代码,而不是讨论。这个实现如何比标准的 Promise 更好?
2021-03-19 10:47:47
这不符合 Promises/A+ 规范的核心要求,即.then()返回具有单独状态的新 Promise 实例。它还仅支持单个拒绝处理程序。
2021-04-08 10:47:47