promise的使用

本文主要介绍一下 promise 在一些场景下的使用,同时还有对 Promises/A+ 规范的一点个人理解。原本是想总体的讲一下JavaScript异步编程的内容,后由于内容很多,能力有限,目前计划分成几部分。

Promise A+ 规范

为了更好的表述,我把 Promise A+ 规范拆成了两部分介绍:

第一部分规范:

  1. 一个 Promise 的当前状态必须为以下三种状态中的一种:等待态(Pending)、执行态(Fulfilled)和拒绝态(Rejected)。

    图片

  2. 一个 Promise 必须提供一个 then 方法以访问其当前值、终值和据因。

    promise.then(onFulfilled, onRejected);

第二部分规范:

  1. Promise then 方法需要 return 一个新的 Promise 出来,如下:

    promise2 = promise1.then(onFulfilled, onRejected);
  2. 如果 onFulfilled 不是函数且 promise1 成功执行, promise2 必须成功执行并返回相同的值;如果 onRejected 不是函数且 promise1 拒绝执行, promise2 必须拒绝执行并返回相同的据因。

  3. 如果 promise 本身状态变更到 Fulfilled 之后,返回调用 onFulfilledonRejected 的解析值 x ,与新的 promise2 进行 promise 的解析过程 [[Resolve]](promise2, x)x 的取值不同,有不同的情况:

  4. x 为一个 promise,则使 promise2 接受 x 的状态:

    var promise2 = new Promise(function(resolve, reject) {

    resolve();
    }).then(function(data) {
    // 对应于 x 的返回值
    return new Promise(function(resolve, reject) {
    resolve('x = promise');
    });
    });

    promise2.then(function(data) {
    // 打印 x = promise
    console.log(data);
    });

    为了看的清晰一点,我把链式拆开了,正常是这样子:

    new Promise(function(resolve, reject) {

    resolve();
    }).then(function(data) {
    // 对应于 x 的返回值
    return new Promise(function(resolve, reject) {
    resolve('x = promise');
    });
    }).then(function(data) {
    // 打印 x = promise
    console.log(data);
    });
  5. x 为一个对象或者函数,如果有 then 方法,将会执行 then 方法, then 方法 this 指向 x 本身,如下:

    new Promise(function(resolve, reject) {

    resolve();
    }).then(function(data){
    // 对应于x的返回值
    return {
    a: 1,
    then: function(resolve, reject) {
    // 打印 1
    console.log(this.a);
    resolve({a: 2});
    }
    };
    }).then(function(data){
    // 打印 2
    console.log(data.a);
    });
  6. 如果 x 没有 then 方法,那么, x 将会做为值来 满足 promise2 ,如下:

    new Promise(function(resolve, reject){

    resolve();
    }).then(function(data){
    // 对应于x的返回值
    return {
    a: 1
    };
    }).then(function(data){
    // 打印 1
    console.log(data.a);
    });

简单实现的 Promise 对象

这是一个简单实现的 Promise 对象,根据我在上面自己分出来的第一部分规范,实现了『有状态』和 then 方法。

function Promise(fn) {
var state = 'pending';
var value;
var deferred;

function resolve(newValue) {
value = newValue;
state = 'resolved';

if(deferred) {
handle(deferred);
}
}

function handle(onResolved) {
if(state === 'pending') {
deferred = onResolved;
return;
}

onResolved(value);
}

this.then = function(onResolved) {
handle(onResolved);
};

fn(resolve);
}

new Promise 的时候:

  1. 执行 fn1 并把 resolve 方法交给 fn1 准备调用
  2. 同步执行 then 方法,将 fn2 作为参数调用 handle 方法
    1. 如果 resolve同步执行,那么 state 变更为 resolved ,紧接着 onResolved 也立即执行
    2. 如果 resolve 没有同步执行,那么 state 依旧是 pending ,那么将 onResolved 的引用保存起来,等到 resolve 异步执行的时候,再调用 onResolved 方法
new Promise(function fn1 (resolve) {

setTimeout(function() {
resolve('eventual value');
}, 1000);
}).then(function fn2 (data) {
// 等待 1000ms 后,打印 eventual value
console.log(data);
});

接下来根据我在上面自己分出来的第二部分规范,下面的实现增加了链式和穿透的特性。

function Promise(fn) {
var state = 'pending';
var value;
var deferred = null;

function resolve(newValue) {
// 这部分是新增的,见注一
if(newValue && typeof newValue.then === 'function') {
newValue.then(resolve);
return;
}
value = newValue;
state = 'resolved';

if(deferred) {
handle(deferred);
}
}

function handle(handler) {
if(state === 'pending') {
deferred = handler;
return;
}
// 这部分是新增的,见注二
if(!handler.onResolved) {
handler.resolve(value);
return;
}

var ret = handler.onResolved(value);
handler.resolve(ret);
}

this.then = function(onResolved) {
return new Promise(function(resolve) {
handle({
onResolved: onResolved,
resolve: resolve
});
});
};

fn(resolve);
}

由于 then() 永远返回一个新的 Promise 对象,导致每次都至少有一个 promise 对象被创建、解决然后被忽略,这就产生了一定程度了内存浪费。

then 方法返回新的 Promise 对象,这个 promise2resolve 的值是 promise1 的返回值。 handle() 函数的最后两行体现了这一点, handler 对象保存了 onResolved() 回调函数和 resolve() 函数的引用。在链式调用中保存了多个 resolve() 函数的拷贝,每一个 promise 对象的内部都拥有一个自己的 resolve() 方法,并在闭包中运行。 这建立起了第一个 promise 与第二个 promise 之间联系的桥梁。

new Promise(function(resolve) {

setTimeout(function() {
resolve('promise1 eventual value');
}, 1000);
}).then(function(data) {
// 等待 1000ms 后,打印 promise1 eventual value
console.log(data);

return {
then: function(resolve) {
setTimeout(function() {
resolve('promise2 eventual value');
}, 1000);
}
}
}).then(function(data) {
// 等待 2000ms 后,打印 promise2 eventual value
console.log(data);
});

注一: then 方法内部的返回值可以是 Promise 对象, 如果是要通过 then 方法获取终值,对应第二部分规范第4和5条。
注二: new Promise.then().then(function (data) {}) 里面第一个 then 会被跳过, promise 也会用上一个 promise 返回的终值来传递,对应第二部分规范第2条。

new Promise(function(resolve) {

setTimeout(function() {
resolve('promise1 eventual value');
}, 1000);
}).then().then(function(data) {
// 等待 1000ms 后,打印 promise1 eventual value
console.log(data);
});

上面的代码一直忽略掉一个问题,错误处理,这里不做引述,原因是我觉得实现方式类似,需要注意的点会在下面讲。

Promise 使用

Promise.all()

当我们想使用 forEach() 的时候,我们可以使用 Promise.all()Promise.all() 以一个 promise 对象组成的数组为输入,返回另一个 promise 对象。这个对象的状态只会在数组中所有的 promise 对象的状态都变为 resolved 的时候才会变成 resolved 。可以将其理解为异步的 for 循环。注意的是,如果输入的一系列 promise 对象中,有一个的状态变为 rejected ,那么 all() 返回的 promise 对象的状态也会变为 rejected

var array = [];
for (var i = 0; i < 4; i++) {

(function() {
var a = i;
array.push(new Promise(function(resolve) {

setTimeout(function() {
resolve(a);
}, a * 1000);
}));
})(i);
}

new Promise(function(resolve) {

resolve();
}).then(function() {

return Promise.all(array);
}).then(function(data) {
// 等待 3000ms 后, 打印 [ 0, 1, 2, 3 ]
console.log(data);
});

Promise.resolve()

可以将同步代码包装成 promise 形式,后面可以加 .then() 或者 .catch() ,我觉得在封装接口的时候很适用。

new Promise(function (resolve, reject) {
resolve(someSynchronousValue);
}).then(...);

精简为:

Promise.resolve(someSynchronousValue).then(...);

cacth() 和 then(null, …) 并不完全相同

下面两个代码片段是等价的:

somePromise().catch(function (err) {
// handle error
});

somePromise().then(null, function (err) {
// handle error
});

但是,这并不意味着下面的两个代码片段是等价的

somePromise().then(function () {
return someOtherPromise();
}).catch(function (err) {
// handle error
});

somePromise().then(function () {
return someOtherPromise();
}, function (err) {
// handle error
});

结论就是,当使用 then(resolveHandler, rejectHandler)rejectHandler 不会捕获在 resolveHandler 中抛出的错误。

总结

最近学了些『规范』,越发觉得自己在编程这条路上可能很能走的很远了。。。

参考文档:
Promises/A+规范
JavaScript Promise 探微
谈谈使用 promise 时候的一些反模式
从Promise的Then说起