Javascript 采用回调函数(callback)来处理异步编程。从同步编程到异步回调编程有一个适应的过程,可是若是出现多层回调嵌套,也就是咱们常说的厄运的回调金字塔(Pyramid of Doom),绝对是一种糟糕的编程体验。因而便有了 CommonJS 的 Promises/A 规范,用于解决回调金字塔问题。本文先介绍 Promises 相关规范,而后再经过解读一个迷你的 Promises 以加深理解。javascript
一个 Promise 对象表明一个目前还不可用,可是在将来的某个时间点能够被解析的值。它容许你以一种同步的方式编写异步代码。例如,若是你想要使用 Promise API 异步调用一个远程的服务器,你须要建立一个表明数据将会在将来由 Web 服务返回的 Promise 对象。惟一的问题是目前数据还不可用。当请求完成并从服务器返回时数据将变为可用数据。在此期间, Promise 对象将扮演一个真实数据的代理角色。接下来,你能够在 Promise 对象上绑定一个回调函数,一旦真实数据变得可用这个回调函数将会被调用。java
Promise 对象曾经以多种形式存在于许多语言中。git
Javascript 中最多见的反模式作法是回调内部再嵌套回调。github
// 回调金字塔 asyncOperation(function(data){ // 处理 `data` anotherAsync(function(data2){ // 处理 `data2` yetAnotherAsync(function(){ // 完成 }); }); });
引入 Promises 以后的代码算法
promiseSomething() .then(function(data){ // 处理 `data` return anotherAsync(); }) .then(function(data2){ // 处理 `data2` return yetAnotherAsync(); }) .then(function(){ // 完成 });
Promises 将嵌套的 callback ,改形成一系列的.then
的连缀调用,去除了层层缩进的糟糕代码风格。 Promises 不是一种解决具体问题的算法,而已一种更好的代码组织模式。接受新的组织模式同时,也逐渐以全新的视角来理解异步调用。编程
各个语言平台都有相应的 Promise 实现promise
下面我来相信了解一下 javascript 语言环境下各个规范的一些细节。服务器
promise 表示一个最终值,该值由一个操做完成时返回。异步
完成
,或者未完成转换成失败 。promise 有一个 then 方法, then 方法能够接受 3 个函数做为参数。前两个函数对应 promise 的两种状态 fulfilled 和 rejected 的回调函数。第三个函数用于处理进度信息(对进度回调的支持是可选的)。async
promiseSomething().then(function(fulfilled){ //当 promise 状态变成 fulfilled 时,调用此函数 },function(rejected){ //当 promise 状态变成 rejected 时,调用此函数 },function(progress){ //当返回进度信息时,调用此函数 });
若是 promise 支持以下连个附加方法,称之为可交互的 promise
在 Promises/A 的基础上, Promises/B 定义了一组 promise 模块须要实现的 API
when(value, callback, errback_opt)
若是 value 不是一个 promise ,那么下一事件循环 callback 会被调用, value 做为 callback 的传入值。若是 value 是一个 promise , promise 的状态已经完成或者变成完成时,那么下一事件循环 callback 会被调用, resolve 的值会被传入 callback ; promise 的状态已经失败或者变成失败时,那么下一事件循环 errback 会被调用, reason 会做为失败的理由传入 errback 。
asap(value, callback, errback_opt)
与 when 最大的区别,若是 value 不是一个 promise ,会被当即执行,不会等到下一事件循环。
enqueue(task Function)
尽量快地在接下来的事件循环调用 task 方法。
get(object, name)
返回一个得到对象属性的 promise 。
post(object, name, args)
返回一个调用对象方法的 promise 。
put(object, name, value)
返回一个修改对象属性的 promise 。
del(object, name)
返回一个删除对象属性的 promise 。
makePromise(descriptor Object, fallback Function)
返回一个 promise 对象,该对象必须是一个可调用的函数,也多是可被实例化的构造函数。
{ "when": function(errback){...}, "get": function(name){...}, "put": function(name, value){...}, "post": function(name, args){...}, "del": function(name){...}, }
上面每个注册的 handle 都返回一个 resolved value 或者 promise 。
defer()
返回一个对象,该对象包含一个 resolve(value) 方法和一个 promise 属性。 当 resolve(value) 方法被第一次调用时, promise 属性的状态变成 完成,全部以前或以后观察该 promise 的 promise 的状态都被转变成 完成。 value 参数若是不是一个 promise ,会被包装成一个 promise 的 ref 。 resolve 方法会忽略以后的全部调用。
reject(reason String)
返回一个被标记为 失败 的 promise 。 一个失败的 promise 上被调用 when(message) 方法时,会采用以下两种方法之一
ref(value)
若是 value 是 promise 对象,返回 value 自己。不然,返回一个 resolved 的 promise ,携带以下 handle 。
isPromise(value) Boolean
判断一个对象是不是 promise
method(name String)
得到一个返回 name 对应方法的 promise 。返回值是 “get”, “put”, “del” 和 “post” 对应的方法,可是会在下一事件循环返回。
为了增长不一样 promise 实现之间的可互操做性, Promises/D 规范对 promise 对象和 Promises/B 规范作了进一步的约定。以达到鸭子类型的效果( Duck-type Promise )。
简单来讲 Promises/D 规范,作了两件事情,
Promise 对象必须是实现 promiseSend
方法。
promiseSend
方法就能够甄别为 promise 对象promiseSend
方法必须接受一个操做名称,做为第一个参数when
,此时第三个参数必须是 rejection 回调。
get
,此时第三个参数为属性名(字符串类型)put
,此时第三个参数为属性名(字符串类型),第四个参数为新属性值。del
,此时第三个参数为属性名post
,此时第三个参数为方法的属性名,接下来的变参为方法的调用参数isDef
promiseSend
方法的第二个参数为 resolver 方法promiseSend
方法可能接受变参promiseSend
方法必须返回undefined
Promises/D 规范中对 Promises/B 规范中定义的 ref 、 reject 、 def 、 defer 方法作了进一步细致的约束,此处略去这些细节。
前面提到的 Promises/A/B/D 规范都是有 CommonJS 组织提出的, Promises/A+是有一个自称为Promises/A+ 组织发布的,该规范是以 Promises/A 做为基础进行补充和修订,旨在提升 promise 实现之间的可互操做性。
Promises/A+ 对.then
方法进行细致的补充,定义了细致的Promise Resolution Procedure流程,而且将.then
方法做为 promise 的对象甄别方法。
此外, Promises/A+ 还提供了兼容性测试工具,以肯定各个实现的兼容性。
上面扯了这么多规范,如今咱们看看如何实现一个简单而短小的 Promise 。
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 or error once FULFILLED or REJECTED var value = null; // store sucess & failure handlers attached by calling .then or .done var handlers = []; }
仅支持两种状态变迁, fulfill 和 reject
// ... function Promise() { // ... function fulfill(result) { state = FULFILLED; value = result; } function reject(error) { state = REJECTED; value = error; } }
fulfill 和 reject 方法较为底层,一般更高级的 resolve 方法开放给外部。
// ... function Promise() { // ... function resolve(result) { try { var then = getThen(result); if (then) { doResolve(then.bind(result), resolve, reject) return } fulfill(result); } catch (e) { reject(e); } } }
resolve 方法能够接受一个普通值或者另外一个 promise 做为参数,若是接受一个 promise 做为参数,等待其完成。 promise 不容许被另外一个 promise fulfill ,因此须要开放 resolve 方法。 resolve 方法依赖一些帮助方法定义以下:
/** * Check if a value is a Promise and, if it is, * return the `then` method of that promise. * * @param {Promise|Any} value * @return {Function|Null} */ function getThen(value) { var t = typeof value; if (value && (t === 'object' || t === 'function')) { var then = value.then; if (typeof then === 'function') { return then; } } return null; } /** * Take a potentially misbehaving resolver function and make sure * onFulfilled and onRejected are only called once. * * Makes no guarantees about asynchrony. * * @param {Function} fn A resolver function that may not be trusted * @param {Function} onFulfilled * @param {Function} onRejected */ 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) } }
这里 resolve 和 doResolve 之间的递归很巧妙,用来处理 promise 的层层嵌套( promise 的 value 是一个 promise )。
// ... function Promise(fn) { // ... doResolve(fn, resolve, reject); }
// ... function Promise(fn) { // ... 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) { // ensure we are always asynchronous setTimeout(function () { handle({ onFulfilled: onFulfilled, onRejected: onRejected }); }, 0); } // ... }
// ... function Promise(fn) { // ... 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); } }); }); } // ... }
jQuery 1.8 以前的版本, jQuery 的 then 方法只是一种能够同时调用 done 、 fail 和 progress 这三种回调的速写方法,而 Promises/A 规范的 then 在行为上更像是 jQuery 的 pipe 。 jQuery 1.8 修正了这个问题,使 then 成为 pipe 的同义词。不过,因为向后兼容的问题, jQuery 的 Promise 再如何对 Promises/A 示好也不太会招人待见。
此外,在 Promises/A 规范中,由 then 方法生成的 Promise 对象是已执行仍是已拒绝,取决于由 then 方法调用的那个回调是返回值仍是抛出错误。在 JQuery 的 Promise 对象的回调中抛出错误是个糟糕的主意,由于错误不会被捕获。
最后一个例子揭示了,实现 Promise 的关键是实现好 doResolve 方法,在完事之后触发回调。而为了保证异步 setTimeout(fun, 0);
是关键一步。
Promise 一直用得蛮顺手的,其很好的优化了 NodeJS 异步处理时的代码结构。可是对于其工做原理却有些懵懂和好奇。因而花了些经理查阅并翻译了 Promise 的规范,以充分的理解 Promise 的细节。