Der*_*ang 74 javascript promise
像q这样的promise/defer库是如何实现的?我试图阅读源代码,但发现它很难理解,所以我认为如果有人能从高层次向我解释在单线程JS环境中用于实现promise的技术是多么好像Node和浏览器.
Kai*_*izo 148
我发现解释比展示一个例子更难,所以这里是一个非常简单的延迟/承诺的实现.
免责声明:这不是一个功能实现,并且缺少Promise/A规范的某些部分,这只是为了解释承诺的基础.
tl; dr:转到Create classes and example部分以查看完整实现.
首先,我们需要使用一组回调创建一个promise对象.我将开始使用对象,因为它更清晰:
var promise = {
callbacks: []
}
Run Code Online (Sandbox Code Playgroud)
现在使用该方法添加回调:
var promise = {
callbacks: [],
then: function (callback) {
callbacks.push(callback);
}
}
Run Code Online (Sandbox Code Playgroud)
我们也需要错误回调:
var promise = {
okCallbacks: [],
koCallbacks: [],
then: function (okCallback, koCallback) {
okCallbacks.push(okCallback);
if (koCallback) {
koCallbacks.push(koCallback);
}
}
}
Run Code Online (Sandbox Code Playgroud)
现在创建将具有承诺的延迟对象:
var defer = {
promise: promise
};
Run Code Online (Sandbox Code Playgroud)
延期需要解决:
var defer = {
promise: promise,
resolve: function (data) {
this.promise.okCallbacks.forEach(function(callback) {
window.setTimeout(function () {
callback(data)
}, 0);
});
},
};
Run Code Online (Sandbox Code Playgroud)
需要拒绝:
var defer = {
promise: promise,
resolve: function (data) {
this.promise.okCallbacks.forEach(function(callback) {
window.setTimeout(function () {
callback(data)
}, 0);
});
},
reject: function (error) {
this.promise.koCallbacks.forEach(function(callback) {
window.setTimeout(function () {
callback(error)
}, 0);
});
}
};
Run Code Online (Sandbox Code Playgroud)
请注意,在超时中调用回调以允许代码始终是异步的.
这就是基本推迟/承诺实施所需要的.
现在让我们将两个对象转换为类,首先是promise:
var Promise = function () {
this.okCallbacks = [];
this.koCallbacks = [];
};
Promise.prototype = {
okCallbacks: null,
koCallbacks: null,
then: function (okCallback, koCallback) {
okCallbacks.push(okCallback);
if (koCallback) {
koCallbacks.push(koCallback);
}
}
};
Run Code Online (Sandbox Code Playgroud)
现在推迟:
var Defer = function () {
this.promise = new Promise();
};
Defer.prototype = {
promise: null,
resolve: function (data) {
this.promise.okCallbacks.forEach(function(callback) {
window.setTimeout(function () {
callback(data)
}, 0);
});
},
reject: function (error) {
this.promise.koCallbacks.forEach(function(callback) {
window.setTimeout(function () {
callback(error)
}, 0);
});
}
};
Run Code Online (Sandbox Code Playgroud)
这是一个使用示例:
function test() {
var defer = new Defer();
// an example of an async call
serverCall(function (request) {
if (request.status === 200) {
defer.resolve(request.responseText);
} else {
defer.reject(new Error("Status code was " + request.status));
}
});
return defer.promise;
}
test().then(function (text) {
alert(text);
}, function (error) {
alert(error.message);
});
Run Code Online (Sandbox Code Playgroud)
如您所见,基本部件简单而小巧.当您添加其他选项时,它会增长,例如多个承诺解析:
Defer.all(promiseA, promiseB, promiseC).then()
Run Code Online (Sandbox Code Playgroud)
或承诺链接:
getUserById(id).then(getFilesByUser).then(deleteFile).then(promptResult);
Run Code Online (Sandbox Code Playgroud)
要了解有关规范的更多信息:CommonJS Promise Specification.请注意,主库(Q,when.js,rsvp.js,node-promise,...)遵循Promises/A规范.
希望我足够清楚.
正如评论中所说,我在这个版本中添加了两件事:
为了能够在解决时调用promise,您需要将状态添加到promise中,并在调用then时检查该状态.如果状态已解决或被拒绝,则只需使用其数据或错误执行回调.
为了能够链接承诺,您需要为每次调用生成一个新的延迟,then并且当承诺被解决/拒绝时,使用回调的结果解析/拒绝新的承诺.因此,当promise完成后,如果回调返回一个新的promise,它将被绑定到随之返回的promise then().如果不是,则使用回调的结果解决承诺.
这是承诺:
var Promise = function () {
this.okCallbacks = [];
this.koCallbacks = [];
};
Promise.prototype = {
okCallbacks: null,
koCallbacks: null,
status: 'pending',
error: null,
then: function (okCallback, koCallback) {
var defer = new Defer();
// Add callbacks to the arrays with the defer binded to these callbacks
this.okCallbacks.push({
func: okCallback,
defer: defer
});
if (koCallback) {
this.koCallbacks.push({
func: koCallback,
defer: defer
});
}
// Check if the promise is not pending. If not call the callback
if (this.status === 'resolved') {
this.executeCallback({
func: okCallback,
defer: defer
}, this.data)
} else if(this.status === 'rejected') {
this.executeCallback({
func: koCallback,
defer: defer
}, this.error)
}
return defer.promise;
},
executeCallback: function (callbackData, result) {
window.setTimeout(function () {
var res = callbackData.func(result);
if (res instanceof Promise) {
callbackData.defer.bind(res);
} else {
callbackData.defer.resolve(res);
}
}, 0);
}
};
Run Code Online (Sandbox Code Playgroud)
推迟:
var Defer = function () {
this.promise = new Promise();
};
Defer.prototype = {
promise: null,
resolve: function (data) {
var promise = this.promise;
promise.data = data;
promise.status = 'resolved';
promise.okCallbacks.forEach(function(callbackData) {
promise.executeCallback(callbackData, data);
});
},
reject: function (error) {
var promise = this.promise;
promise.error = error;
promise.status = 'rejected';
promise.koCallbacks.forEach(function(callbackData) {
promise.executeCallback(callbackData, error);
});
},
// Make this promise behave like another promise:
// When the other promise is resolved/rejected this is also resolved/rejected
// with the same data
bind: function (promise) {
var that = this;
promise.then(function (res) {
that.resolve(res);
}, function (err) {
that.reject(err);
})
}
};
Run Code Online (Sandbox Code Playgroud)
正如你所看到的,它已经增长了很多.
Q在实现方面是一个非常复杂的promise库,因为它旨在支持流水线和RPC类型的场景.我有我自己的非常裸露的骨头实现的承诺/ A +规格在这里.
原则上它很简单.在解决/解决了promise之前,您可以通过将其推入数组来记录任何回调或错误.当承诺得到解决后,您可以调用适当的回调或错误,并记录承诺的结果(以及是否已履行或拒绝).在它结束后,你只需用存储的结果调用回调或错误.
这给了你大概的语义done.要构建then你只需要返回一个新的promise,它会通过调用callbacks/errbacks来解决.
如果您对完整的承诺实现的开发有充分的理解,并且支持RPC和像Q这样的流水线,那么您可以在这里阅读kriskowal的推理.如果你正在考虑实施承诺,这是一个非常好的渐进式方法,我不能高度推荐.即使你只是要使用一个promise库,它也许值得一读.
正如福布斯在他的回答中提到的那样,我记录了制作像Q这样的库所涉及的许多设计决策,这里是https://github.com/kriskowal/q/tree/v1/design.可以这么说,有一个承诺库的级别,以及许多停止在各个级别的库.
在第一级,由Promises/A +规范捕获,promise是最终结果的代理,适用于管理"本地异步".也就是说,它适合于确保以正确的顺序进行工作,并且确保操作的结果简单直接,无论其是否已经结算,或将来是否会发生.它还使一方或多方订阅最终结果同样简单.
问:正如我已经实现的那样,它提供了作为最终,远程或最终+远程结果的代理的承诺.为此,它的设计是颠倒的,具有承诺延迟承诺,履行承诺,拒绝承诺和远程对象承诺的不同实现(最后一个在Q-Connection中实现).它们都共享相同的接口,并通过发送和接收"then"(这对Promises/A +已足够)以及"get"和"invoke"等消息来工作.因此,Q是关于"分布式异步",并且存在于另一层上.
然而,Q实际上是从较高层进行的,其中承诺用于管理诸如你,商人,银行,Facebook,政府 - 不是敌人,甚至是朋友之类的相互可疑方之间的分布式异步,但有时会发生冲突.利益.我实现的Q被设计成API与强化安全承诺兼容(这是分离的原因promise和resolve),希望这将人们介绍的承诺,在使用此API训练他们,并让他们把他们的代码如果他们将来需要在安全的mashup中使用promises,请与他们一起使用.
当然,当你向上移动时,通常会以速度进行权衡.因此,promises实现也可以设计为共存.这就是"可以"的概念进入的地方.每层的Promise库可以设计为使用来自任何其他层的promise,因此多个实现可以共存,用户只能购买他们需要的东西.
所有这些都说,没有理由难以阅读.Domenic和我正在开发一个Q版本,它将更加模块化和平易近人,其中一些分散注意力的依赖关系和解决方案转移到其他模块和包中.值得庆幸的是福布斯,克罗克福德等人通过制作更简单的图书馆填补了教育空白.