基本的Javascript承诺实施尝试
为了更好地理解Javascript中承诺的工作原理,我决定尝试一下,并自己编写基本的实现代码。
基本上我想实现Promises对象(我称之为代码中的Aaa),它将函数作为参数。 这个函数可以调用resolve来resolve
promise,或拒绝reject
。 基本的实现和用法如下。 不知道第二个参数是否可以按照诺言规格接受,但这是我迄今为止。
Aaa=function(f,pause) { console.log("ggg"); var t=this; this.f=f; this.thens=[]; this.resolve=function(g) { for(var i=0;i<t.thens.length;i++) { // try/catch to be used later for dealing with exceptions try { t.thens[i].f(g); t.thens[i].resolve(); } catch(ex) {} } }; // to be implemented later this.reject=function(g) {}; this.then=function(resolve,reject) { // i'm passing true for pause argument as we dont need to execute promise code just yet var nextPromise=new Aaa(resolve,true); this.thens.push(nextPromise); return nextPromise; } if(!pause) this.f(this.resolve,this.reject); } var aaa=new Aaa(function(resolve,reject) { console.log("aaa"); setTimeout(function() { console.log("fff"); resolve("good"); },2000); console.log("bbb"); });
所以现在可以创造,调用和解决承诺。 每个方法都会返回新的Aaa(Promise),这样就可以链接了。 现在下面的代码使用上面创build的promise并链接callback。 then
每个返回新的承诺,在这种情况下,它似乎工作正常:
aaa.then(function(res) { console.log("ccc"); console.log(res); }) .then(function(res) { console.log("ddd"); console.log(res); },function(rej) { console.log("eee"); console.log(rej); });
我得到的输出是:
ggg aaa bbb ggg ggg fff ccc good ddd undefined
然而问题是当其中一个调用返回一个承诺时:
aaa.then(function(res) { console.log("ccc"); console.log(res); // here we return the promise manually. then next then call where "ddd" is output should not be called UNTIL this promise is resolved. How to do that? return new Aaa(function(resolve,reject) { console.log("iii"); setTimeout(function() { console.log("kkk"); resolve("good2"); // reject("bad"); },2000); console.log("jjj"); }).then(function (res) { console.log("lll"); console.log(res); }); }) .then(function(res) { console.log("ddd"); console.log(res); },function(rej) { console.log("eee"); console.log(rej); });
输出是:
ggg aaa bbb ggg ggg fff ccc good ggg iii jjj ggg ddd undefined kkk lll good2
然后,输出ddd
的调用不应被调用,直到我们刚刚添加的返回的promise被parsing。
这将如何最好地实施?
有一些你不在这里处理的情况。 最好的办法就是把这个承诺作为一个状态机来构build:
var PENDING = 0; var FULFILLED = 1; var REJECTED = 2; function Promise() { // store state which can be PENDING, FULFILLED or REJECTED var state = PENDING; // store value once FULFILLED or REJECTED var value = null; // store sucess & failure handlers var handlers = []; }
现在让我们定义一个简单的帮助器,通过其余的实现来使用:
// a function that returns `then` if `value` is a promise, otherwise `null` function getThen(value) { if (result && (typeof result === 'object' || typeof result === 'function')) { var then = value.then; if (typeof then === 'function') { return then; } } return null; }
接下来,我们需要考虑可能发生的每个转换:
var PENDING = 0; var FULFILLED = 1; var REJECTED = 2; function Promise() { // store state which can be PENDING, FULFILLED or REJECTED var state = PENDING; // store value once FULFILLED or REJECTED var value = null; // store sucess & failure handlers var handlers = []; function resolve(result) { try { var then = getThen(result); if (then) { doResolve(then.bind(result), resolve, reject) return } state = FULFILLED; value = result; } catch (e) { reject(e); } } function reject(error) { state = REJECTED; value = error; } }
请注意, resolve
如何能够接受一个承诺作为它的论据,但是一个承诺永远不能用另一个承诺来实现。 所以我们必须处理这个特殊情况。
还要注意,一个承诺只能完成/拒绝一次。 我们也有一个问题,第三方的承诺可能会行事不端,我们应该防范我们的代码。 出于这个原因,我不只是在resolve
调用result.then(resolve, reject)
。 相反,我把它分成一个单独的函数:
/** * Take a potentially misbehaving resolver function and make sure * onFulfilled and onRejected are only called once. * * Makes no guarantees about asynchrony. */ function doResolve(fn, onFulfilled, onRejected) { var done = false; try { fn(function (value) { if (done) return done = true onFulfilled(value) }, function (reason) { if (done) return done = true onRejected(reason) }) } catch (ex) { if (done) return done = true onRejected(ex) } }
所以现在我们有一个完整的状态机,但没有办法观察或触发状态的变化。 让我们开始添加一个方法来触发状态变化,通过传入一个parsing器函数。
function Promise(fn) { if (typeof this !== 'object') throw new TypeError('Promises must be constructed via new'); if (typeof fn !== 'function') throw new TypeError('fn must be a function'); // store state which can be PENDING, FULFILLED or REJECTED var state = PENDING; // store value once FULFILLED or REJECTED var value = null; // store sucess & failure handlers var handlers = []; function resolve(result) { try { var then = getThen(result); if (then) { doResolve(then.bind(result), resolve, reject) return } state = FULFILLED; value = result; } catch (e) { reject(e); } } function reject(error) { state = REJECTED; value = error; } doResolve(fn, resolve, reject); }
正如您所看到的,我们重新使用了doResolve
因为我们有另一个doResolve
信任的parsing器。 fn
可能会多次调用resolve
或reject
,并且可能会引发错误。 我们需要处理所有这些情况(这就是doResolve
所做的)。
我们现在已经完成了状态机,但是我们没有公开关于它处于什么状态的任何信息。让我们尝试添加一个.done(onFulfilled, onRejected)
方法,就像.done(onFulfilled, onRejected)
一样.then
除了它不返回一个Promise和不处理由onFulfilled
和onRejected
引发的错误。
var PENDING = 0; var FULFILLED = 1; var REJECTED = 2; function Promise(fn) { if (typeof this !== 'object') throw new TypeError('Promises must be constructed via new'); if (typeof fn !== 'function') throw new TypeError('fn must be a function'); // store state which can be PENDING, FULFILLED or REJECTED var state = PENDING; // store value once FULFILLED or REJECTED var value = null; // store sucess & failure handlers var handlers = []; function resolve(result) { try { var then = getThen(result); if (then) { doResolve(then.bind(result), resolve, reject) return } state = FULFILLED; value = result; handlers.forEach(handle); handlers = null; } catch (e) { reject(e); } } function reject(error) { state = REJECTED; value = error; handlers.forEach(handle); handlers = null; } function handle(handler) { if (state === PENDING) { handlers.push(handler); } else { if (state === FULFILLED && typeof handler.onFulfilled === 'function') { handler.onFulfilled(value); } if (state === REJECTED && typeof handler.onRejected === 'function') { handler.onRejected(value); } } } this.done = function (onFulfilled, onRejected) { setTimeout(function () { // ensure we are always asynchronous handle({ onFulfilled: onFulfilled, onRejected: onRejected }); }, 0); } doResolve(fn, resolve, reject); }
请注意,我们必须处理在Promise被实现/拒绝之前和之后被调用的.done
的情况。
我们几乎有一个完整的promise实现,但是,正如你在构build自己的实现时已经注意到的那样,我们需要一个返回Promise的.then
方法。
我们可以从.done
构build这个easilly:
this.then = function (onFulfilled, onRejected) { var self = this; return new Promise(function (resolve, reject) { return self.done(function (result) { if (typeof onFulfilled === 'function') { try { return resolve(onFulfilled(result)); } catch (ex) { return reject(ex); } } else { return resolve(result); } }, function (error) { if (typeof onRejected === 'function') { try { return resolve(onRejected(error)); } catch (ex) { return reject(ex); } } else { return reject(error); } }); }); }
请注意,我们现在如何获得您正在挣扎的东西,因为resolve
接受一个Promise并等待它被解决。
NB我还没有testing过这个Promise的实现(虽然这是我所知的最好的)。 您应该testing您针对Promises / A +testing套件( https://github.com/promises-aplus/promises-tests )构build的任何实现,并且还可以findPromises / A +规范( https://github.com/promises -aplus / promises-spec )在计算algorithm的任何特定部分的正确行为方面很有用。 作为最终的资源, 承诺是Promise规范的一个非常小的实现。
(对于完整的Promise实现,向下滚动)。
在你的代码中的一些问题
这是几个问题,但我认为在你的代码中的主要错误是,你把参数给了then
方法,并把它作为一个新的承诺的参数:
this.then=function(resolve,reject) { var nextPromise=new Aaa(resolve,true); // ...
虽然这两个参数都是callback函数,但它们具有不同的签名,并且服务于完全不同的目的:
- promise构造函数的参数是一个要立即 同步执行的callback函数。 一个函数作为第一个parameter passing给它,通过这个函数可以解决你正在创build的许诺。
-
then
方法的(第一个)参数是一个callback函数,它只会在基础promise被parsing时才asynchronous执行,并且parsing值作为parameter passing给它。
你可以在你的代码中看到不同的地方,你把参数作为f属性存储在构造函数中。 你有这两个:
t.thens[i].f(g);
…其中g是解决的价值,但也是这样的:
this.f(this.resolve,this.reject);
参数是函数的地方 当你创buildnextPromise时,实际上首先会用这两个参数调用f ,然后用g参数调用f 。
承诺/ A +标准的实施从头开始
我们可以按照Promises / A +规范的要求来构build自己的Promise实现:
2.1承诺状态
只允许2个状态转换:从挂起到履行,从挂起到拒绝。 没有其他转换是可能的,一旦转换完成,承诺值(或拒绝原因)不应该改变。
这是一个简单的实现,将坚持上述限制。 注释引用上述规范中的编号要求:
function MyPromise(executor) { this.state = 'pending'; this.value = undefined; executor(this.resolve.bind(this), this.reject.bind(this)); } // 2.1.1.1: provide only two ways to transition MyPromise.prototype.resolve = function (value) { if (this.state !== 'pending') return; // 2.1.2.1, 2.1.3.1: cannot transition anymore this.state = 'fulfilled'; // 2.1.1.1: can transition this.value = value; // 2.1.2.2: must have a value } MyPromise.prototype.reject = function (reason) { if (this.state !== 'pending') return; // 2.1.2.1, 2.1.3.1: cannot transition anymore this.state = 'rejected'; // 2.1.1.1: can transition this.value = reason; // 2.1.3.2: must have a reason }
当然,这不提供then
方法,这是Promise的关键:
2.2 then
方法
这是规范的核心。 上面的代码可以被扩展来暴露then
方法,它返回一个promise,并提供asynchronous执行的合适的callback,只有一次,提供多个then
调用,把exception排除在外,等等。
所以下面的代码添加了then
方法,而且是一个单独定义的broadcast
函数,因为它必须在任何状态改变时被调用:这不仅包括then
方法的效果(一个promise被添加到列表中),而且还包括resolve
和reject
方法(状态和价值的变化)。
function MyPromise(executor) { this.state = 'pending'; this.value = undefined; // A list of "clients" that need to be notified when a state // change event occurs. These event-consumers are the promises // that are returned by the calls to the `then` method. this.consumers = []; executor(this.resolve.bind(this), this.reject.bind(this)); } // 2.1.1.1: provide only two ways to transition MyPromise.prototype.resolve = function (value) { if (this.state !== 'pending') return; // 2.1.2.1, 2.1.3.1: cannot transition anymore this.state = 'fulfilled'; // 2.1.1.1: can transition this.value = value; // 2.1.2.2: must have a value this.broadcast(); } MyPromise.prototype.reject = function (reason) { if (this.state !== 'pending') return; // 2.1.2.1, 2.1.3.1: cannot transition anymore this.state = 'rejected'; // 2.1.1.1: can transition this.value = reason; // 2.1.3.2: must have a reason this.broadcast(); } // A promise's then method accepts two arguments: MyPromise.prototype.then = function(onFulfilled, onRejected) { var consumer = new MyPromise(function () {}); // 2.2.1.1 ignore onFulfilled if not a function consumer.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : null; // 2.2.1.2 ignore onRejected if not a function consumer.onRejected = typeof onRejected === 'function' ? onRejected : null; // 2.2.6.1, 2.2.6.2: .then() may be called multiple times on the same promise this.consumers.push(consumer); // It might be that the promise was already resolved... this.broadcast(); // 2.2.7: .then() must return a promise return consumer; }; MyPromise.prototype.broadcast = function() { var promise = this; // 2.2.2.1, 2.2.2.2, 2.2.3.1, 2.2.3.2 called after promise is resolved if (this.state === 'pending') return; // 2.2.6.1, 2.2.6.2 all respective callbacks must execute var callbackName = this.state == 'fulfilled' ? 'onFulfilled' : 'onRejected'; var resolver = this.state == 'fulfilled' ? 'resolve' : 'reject'; // 2.2.4 onFulfilled/onRejected must be called asynchronously setTimeout(function() { // 2.2.6.1, 2.2.6.2 traverse in order, 2.2.2.3, 2.2.3.3 called only once promise.consumers.splice(0).forEach(function(consumer) { try { var callback = consumer[callbackName]; // 2.2.1.1, 2.2.1.2 ignore callback if not a function, else // 2.2.5 call callback as plain function without context if (callback) { // TODO: 2.2.7.1. For now we simply fulfill the promise: consumer.resolve(callback(promise.value)); } else { // 2.2.7.3 resolve in same way as current promise consumer[resolver](promise.value); } } catch (e) { // 2.2.7.2 consumer.reject(e); }; }) }); };
这几乎涵盖了所有的事情,除了TODO:
评论,所谓的Promise Resolution Procedure必须被称为:
2.3承诺解决程序
这是一个以不同的方式处理值(或甚至承诺)的值的过程:过程将不会按照原样返回值,而是对该值执行then
方法,并asynchronous地使用从该callback接收到的值执行promise。 在规范中没有提到,但是不仅在then
方法中执行,而且当主要的承诺以这样的值解决时,这是有趣的。
所以现有的resolve
方法应该用这个“承诺解决程序”来取代,这个程序会叫原来的resolve
方法。 原来的一个可以称为“履行”,表明它将永远履行承诺:
function MyPromise(executor) { this.state = 'pending'; this.value = undefined; // A list of "clients" that need to be notified when a state // change event occurs. These event-consumers are the promises // that are returned by the calls to the `then` method. this.consumers = []; executor(this.resolve.bind(this), this.reject.bind(this)); } // 2.1.1.1: provide only two ways to transition MyPromise.prototype.fulfill = function (value) { if (this.state !== 'pending') return; // 2.1.2.1, 2.1.3.1: cannot transition anymore this.state = 'fulfilled'; // 2.1.1.1: can transition this.value = value; // 2.1.2.2: must have a value this.broadcast(); } MyPromise.prototype.reject = function (reason) { if (this.state !== 'pending') return; // 2.1.2.1, 2.1.3.1: cannot transition anymore this.state = 'rejected'; // 2.1.1.1: can transition this.value = reason; // 2.1.3.2: must have a reason this.broadcast(); } // A promise's then method accepts two arguments: MyPromise.prototype.then = function(onFulfilled, onRejected) { var consumer = new MyPromise(function () {}); // 2.2.1.1 ignore onFulfilled if not a function consumer.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : null; // 2.2.1.2 ignore onRejected if not a function consumer.onRejected = typeof onRejected === 'function' ? onRejected : null; // 2.2.6.1, 2.2.6.2: .then() may be called multiple times on the same promise this.consumers.push(consumer); // It might be that the promise was already resolved... this.broadcast(); // 2.2.7: .then() must return a promise return consumer; }; MyPromise.prototype.broadcast = function() { var promise = this; // 2.2.2.1, 2.2.2.2, 2.2.3.1, 2.2.3.2 called after promise is resolved if (this.state === 'pending') return; // 2.2.6.1, 2.2.6.2 all respective callbacks must execute var callbackName = this.state == 'fulfilled' ? 'onFulfilled' : 'onRejected'; var resolver = this.state == 'fulfilled' ? 'resolve' : 'reject'; // 2.2.4 onFulfilled/onRejected must be called asynchronously setTimeout(function() { // 2.2.6.1, 2.2.6.2 traverse in order, 2.2.2.3, 2.2.3.3 called only once promise.consumers.splice(0).forEach(function(consumer) { try { var callback = consumer[callbackName]; // 2.2.1.1, 2.2.1.2 ignore callback if not a function, else // 2.2.5 call callback as plain function without context if (callback) { // 2.2.7.1. execute the Promise Resolution Procedure: consumer.resolve(callback(promise.value)); } else { // 2.2.7.3 resolve in same way as current promise consumer[resolver](promise.value); } } catch (e) { // 2.2.7.2 consumer.reject(e); }; }) }); }; // The Promise Resolution Procedure: will treat values that are thenables/promises // and will eventually call either fulfill or reject/throw. MyPromise.prototype.resolve = function(x) { var wasCalled, then; // 2.3.1 if (this === x) { throw new TypeError('Circular reference: promise value is promise itself'); } // 2.3.2 if (x instanceof MyPromise) { // 2.3.2.1, 2.3.2.2, 2.3.2.3 x.then(this.resolve.bind(this), this.reject.bind(this)); } else if (x === Object(x)) { // 2.3.3 try { // 2.3.3.1 then = x.then; if (typeof then === 'function') { // 2.3.3.3 then.call(x, function resolve(y) { // 2.3.3.3.3 don't allow multiple calls if (wasCalled) return; wasCalled = true; // 2.3.3.3.1 recurse this.resolve(y); }.bind(this), function reject(reasonY) { // 2.3.3.3.3 don't allow multiple calls if (wasCalled) return; wasCalled = true; // 2.3.3.3.2 this.reject(reasonY); }.bind(this)); } else { // 2.3.3.4 this.fulfill(x); } } catch(e) { // 2.3.3.3.4.1 ignore if call was made if (wasCalled) return; // 2.3.3.2 or 2.3.3.3.4.2 this.reject(e); } } else { // 2.3.4 this.fulfill(x); } }
这现在是Promises / A +兼容,至less它通过了testing套件。 然而,Promise对象暴露了太多的方法和属性:
只有一个承诺对象
上面构build的构造函数创build的东西更像是一个Deferred对象,即暴露了resolve
和reject
方法。 更糟糕的是, status
和value
属性是可写的。 所以,把它当作一个不安全的Deferred对象的构造函数会更合乎逻辑,并且创build一个单独的Promise构造函数来构build它,但是只公开所需要的:一个then
方法和一个构造函数callback,可以访问resolve
和reject
。
延迟对象可以在没有构造函数callback参数的情况下执行,并通过promise
属性提供对纯诺言对象的访问:
function Deferred() { this.state = 'pending'; this.value = undefined; this.consumers = []; this.promise = Object.create(MyPromise.prototype, { then: { value: this.then.bind(this) } }); } // 2.1.1.1: provide only two ways to transition Deferred.prototype.fulfill = function (value) { if (this.state !== 'pending') return; // 2.1.2.1, 2.1.3.1: cannot transition anymore this.state = 'fulfilled'; // 2.1.1.1: can transition this.value = value; // 2.1.2.2: must have a value this.broadcast(); } Deferred.prototype.reject = function (reason) { if (this.state !== 'pending') return; // 2.1.2.1, 2.1.3.1: cannot transition anymore this.state = 'rejected'; // 2.1.1.1: can transition this.value = reason; // 2.1.3.2: must have a reason this.broadcast(); } // A promise's then method accepts two arguments: Deferred.prototype.then = function(onFulfilled, onRejected) { var consumer = new Deferred(); // 2.2.1.1 ignore onFulfilled if not a function consumer.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : null; // 2.2.1.2 ignore onRejected if not a function consumer.onRejected = typeof onRejected === 'function' ? onRejected : null; // 2.2.6.1, 2.2.6.2: .then() may be called multiple times on the same promise this.consumers.push(consumer); // It might be that the promise was already resolved... this.broadcast(); // 2.2.7: .then() must return a promise return consumer; }; Deferred.prototype.broadcast = function() { var promise = this; // 2.2.2.1, 2.2.2.2, 2.2.3.1, 2.2.3.2 called after promise is resolved if (this.state === 'pending') return; // 2.2.6.1, 2.2.6.2 all respective callbacks must execute var callbackName = this.state == 'fulfilled' ? 'onFulfilled' : 'onRejected'; var resolver = this.state == 'fulfilled' ? 'resolve' : 'reject'; // 2.2.4 onFulfilled/onRejected must be called asynchronously setTimeout(function() { // 2.2.6.1, 2.2.6.2 traverse in order, 2.2.2.3, 2.2.3.3 called only once promise.consumers.splice(0).forEach(function(consumer) { try { var callback = consumer[callbackName]; // 2.2.1.1, 2.2.1.2 ignore callback if not a function, else // 2.2.5 call callback as plain function without context if (callback) { // 2.2.7.1. execute the Promise Resolution Procedure: consumer.resolve(callback(promise.value)); } else { // 2.2.7.3 resolve in same way as current promise consumer[resolver](promise.value); } } catch (e) { // 2.2.7.2 consumer.reject(e); }; }) }); }; // The Promise Resolution Procedure: will treat values that are thenables/promises // and will eventually call either fulfill or reject/throw. Deferred.prototype.resolve = function(x) { var wasCalled, then; // 2.3.1 if (this.promise === x) { throw new TypeError('Circular reference: promise value is promise itself'); } // 2.3.2 if (x instanceof MyPromise) { // 2.3.2.1, 2.3.2.2, 2.3.2.3 x.then(this.resolve.bind(this), this.reject.bind(this)); } else if (x === Object(x)) { // 2.3.3 try { // 2.3.3.1 then = x.then; if (typeof then === 'function') { // 2.3.3.3 then.call(x, function resolve(y) { // 2.3.3.3.3 don't allow multiple calls if (wasCalled) return; wasCalled = true; // 2.3.3.3.1 recurse this.resolve(y); }.bind(this), function reject(reasonY) { // 2.3.3.3.3 don't allow multiple calls if (wasCalled) return; wasCalled = true; // 2.3.3.3.2 this.reject(reasonY); }.bind(this)); } else { // 2.3.3.4 this.fulfill(x); } } catch(e) { // 2.3.3.3.4.1 ignore if call was made if (wasCalled) return; // 2.3.3.2 or 2.3.3.3.4.2 this.reject(e); } } else { // 2.3.4 this.fulfill(x); } } function MyPromise(executor) { // A Promise is just a wrapper around a Deferred, exposing only the `then` // method, while `resolve` and `reject` are available in the constructor callback var df = new Deferred(); // Provide access to the `resolve` and `reject` methods via the callback executor(df.resolve.bind(df), df.reject.bind(df)); return df.promise; }
这个代码有几种可能的优化方式,比如使Deferred方法成为私有函数,并将相似的代码合并成较短的代码块,但是现在它已经很清楚地显示了每个需求的覆盖范围。
快乐的编码。
这一切似乎非常复杂。 我认为有一个非常简单的recursion解决scheme。 为了简洁起见,我将忽略这个拒绝,但除了你停止链之外,它和解决方法几乎是一样的。
var MyPromise = function(callback) { this.callbacks = []; callback(this.resolve.bind(this)); } MyPromise.prototype.resolve = function(data) { var callback = this.callbacks.pop(); var result = callback(data); if (!result) return; if (result instanceof MyPromise) { var resolve = this.resolve.bind(this); return result.then(function(d) { return resolve(d); }); } return this.resolve(result); } MyPromise.prototype.then = function(callback) { this.callbacks.unshift(callback); return this; }