Promise 简介
(该部分转载自 blog.csdn.net/u010576399/… ) Promise对象是CommonJS工作组提出的一种规范,目的是为异步操作提供统一接口.
那么,什么是Promises?
首先,它是一个对象,也就是说与其他JavaScript对象的用法,没有什么两样;其次,它起到代理作用(proxy),充当异步操作与回调函数之间的中介。它使得异步操作具备同步操作的接口,使得程序具备正常的同步运行的流程,回调函数不必再一层层嵌套。
简单说,它的思想是,每一个异步任务立刻返回一个Promise对象,由于是立刻返回,所以可以采用同步操作的流程。这个Promises对象有一个then方法,允许指定回调函数,在异步任务完成后调用。
比如,异步操作f1返回一个Promise对象,它的回调函数f2写法如下:
(new Promise(f1)).then(f2);
复制代码
这种写法对于多层嵌套的回调函数尤其方便。
// 传统写法
step1(function (value1) {
step2(value1, function(value2) {
step3(value2, function(value3) {
step4(value3, function(value4) {
// ...
});
});
});
});
// Promises的写法
(new Promise(step1))
.then(step2)
.then(step3)
.then(step4);
复制代码
从上面代码可以看到,采用Promises接口以后,程序流程变得非常清楚,十分易读。
注意,为了便于理解,上面代码的Promise对象的生成格式,做了简化。
总的来说,传统的回调函数写法使得代码混成一团,变得横向发展而不是向下发展。Promises规范就是为了解决这个问题而提出的,目标是使用正常的程序流程(同步),来处理异步操作。它先返回一个Promise对象,后面的操作以同步的方式,寄存在这个对象上面。等到异步操作有了结果,再执行前期寄放在它上面的其他操作。
Promises原本只是社区提出的一个构想,一些外部函数库率先实现了这个功能。ECMAScript 6将其写入语言标准,因此目前JavaScript语言原生支持Promise对象。
手动实现一个满足promises-aplus-tests的Promise
promisesaplus.com/ 是一个介绍promise如何实现的一个网站. 根据该网站提供的介绍信息, 我们可以尝试自己写一个promise并使用提供的promises-aplus-tests工具对所写的promise进行测试.
Step1
首先 实现promise最基本的功能: 即在promise创建以后执行执行器中的代码,在then的时候相应的函数得以执行. 需要注意的是,当执行器中有错误抛出的时候,应该捕获错误并直接执行reject
//实现功能: 当执行器中调用resolve(),则then中只执行onFulfiled方法,执行器中调用reject(),则then中只执行onRejected方法, 当两个方法都有的时候,以先执行的方法为准,后执行的方法对then不产生影响.
function Promise (executor) { //执行器
let self = this;
self.status = 'pending'; //引入状态,对两个方法都有的情况进行区分
self.value = undefined; //默认值
self.reason = undefined;
function resolve(data_value) {
if(self.status === 'pending') {
self.status = 'resolved';
self.value = data_value;
}
}
function reject(data_reason) {
if(self.status === 'pending') {
self.status = 'rejected';
self.reason = data_reason;
}
}
//如果executor是同步代码 进行try catch获取其中的异常 如果有异常 把异常传到reject
try {
executor(resolve, reject);
} catch (e) {
reject(e); //调用reject并把捕获的error作为参数传给reject
}
}
Promise.prototype.then = function (onFulfiled, onRejected) {
let self = this;
if(self.status === 'resolved') {
onFulfiled(self.value);
}
if(self.status === 'rejected') {
onRejected(self.value);
}
}
复制代码
step2
那么问题来了, 如果执行器中有异步代码, 上面的实现方法就会出问题,因为在执行
executor(resolve, reject);
复制代码
的时候,异步代码不会立即执行, then中没有判断异步代码是否已经执行的机制. 我们的解决方案是: 在then中判定状态是否为pending,如果状态为pending(此时为构造函数new Promise()执行期间), 则把then中的执行函数onFulfiled, onRejected先存入队列(用array实现),当状态改变后执行这些方法.
function Promise (executor) { //执行器
let self = this;
self.status = 'pending';
self.value = undefined; //默认值
self.reason = undefined;
self.onResolvedCallbacks = []; //存放then成功的回调 数组
self.onRejectedCallbacks = []; //存放then失败的回调 数组
function resolve(data_value) {
if(self.status === 'pending') {
self.status = 'resolved';
self.value = data_value;
self.onResolvedCallbacks.forEach(function(fn) { //调用resolve的时候执行保存在onRejectedCallbacks的函数
fn();
})
}
}
function reject(data_reason) {
if(self.status === 'pending') {
self.status = 'rejected';
self.reason = data_reason;
self.onRejectedCallbacks.forEach(function(fn) { //调用resolve的时候执行保存在onRejectedCallbacks的函数
fn();
})
}
}
try {
executor(resolve, reject); //当executor中有异步代码时 这部分不会立即执行(但是前面的部分在new的时候还是会执行)
} catch (e) {
reject(e);
}
}
Promise.prototype.then = function (onFulfiled, onRejected) {
let self = this;
if(self.status === 'resolved') {
onFulfiled(self.value);
}
if(self.status === 'rejected') {
onRejected(self.reason);
}
//当调用then时可能没成功 也没失败
if(self.status === 'pending') { //此时没有resolve也没有reject
self.onResolvedCallbacks.push(function(){ //用数组是为了保证在异步时有多次promise.then的情况
onFulfiled(self.value);
});
self.onRejectedCallbacks.push(function(){
onRejected(self.reason);
});
}
}
复制代码
step3
到现在为止,我们仅仅完成了promise最基本的功能, 但是promise中一个重要的功能: then的链式调用尚未实现. 解决的思路类似于jquery的链式调用, 区别则是:jquery是返回this 这里则是返回一个新的promise. 我们在then中新建一个变量promise2. 以
self.status === 'resolved'
复制代码
情况为例 将之前的代码修改为:
if(self.status === 'resolved') {
promise2 = new Promise(function(resolve, reject){ //将前一次then中的执行函数放入新的Promise的executor得到promise2作为下次then的返回值
onFulfiled(self.value); //注意这时返回的promise的执行器执行的是onFulfiled函数 而不是resolve或者reject.
})
}
复制代码
但是以上代码中, promise2在创建的时候, 并没有设定resolve/reject的规则,因此只能算作半成品. 根据规定, 如果onFulfile/onRejected有返回值, 则将返回值作为resolve/reject的参数传入,这样,下一次.then就有状态,不再是无根之木. 根据返回值的不同, 又将返回值分别以普通值和promise进行分别处理. 为此我们引入了一个统一的处理方法,以resolve()为例, 对应的处理方法我们取名为resolvePromise(promise2, x, resolve, reject) 其中x为onFulfiled的返回值. resolvePromise代码如下所示:
function resolvePromise(promise2, x, resolve, reject) {
//有可能这里返回的x是别人的promise 要尽可能允许其他人乱写
if(promise2 === x) {//这里应该报一个循环引用的类型错误
return reject(new TypeError('循环引用'));
}
//看x是不是一个promise promise应该是一个对象
if (x!== null && (typeof x ==='object' ||typeof x === 'function')) {
//可能是promise 看这个对象中是否有then 如果有 姑且作为promise 用try catch防止报错
try {
let then = x.then;
if (typeof then === 'function') {
then.call(x, function(y) {
//成功
resolvePromise(promise2, y, resolve, reject)
}, function (err) {
//失败
reject(err);
})
} else {
resolve(x) //如果then不是函数 则把x作为返回值.
}
} catch (e) {
reject(e)
}
} else { //普通值
return resolve(x)
}
}
复制代码
相应的
self.status === 'resolved'
复制代码
时的代码则为:
if(self.status === 'resolved') {
promise2 = new Promise(function(resolve, reject){
let x = onFulfiled(self.value);
resolvePromise(promise2, x, resolve, reject);
})
}
复制代码
为例保证代码的通用性,考虑到有可能其他人的promise写的不正确,可能会既调用成功又调用失败的情况, 我们应当在代码中对返回的promise进行判断: 如果两个都调用 先调用谁 另一个忽略掉. 为此引入一个变量called.
function resolvePromise(promise2, x, resolve, reject) {
//有可能这里返回的x是别人的promise 要尽可能允许其他人乱写
if(promise2 === x) {//这里应该报一个循环引用的类型错误
return reject(new TypeError('循环引用'));
}
//看x是不是一个promise promise应该是一个对象
let called; //表示是否调用过成功或者失败
if (x!== null && (typeof x ==='object' ||typeof x === 'function')) {
//可能是promise 看这个对象中是否有then 如果有 姑且作为promise 用try catch防止报错
try {
let then = x.then;
if (typeof then === 'function') {
//成功
then.call(x, function(y) {
if (called) return
called = true;
resolvePromise(promise2, y, resolve, reject)
}, function(err) {
if (called) return
called = true;
reject(err);
})
} else {
resolve(x) //如果then不是函数 则把x作为返回值.
}
} catch (e) {
if (called) return
called = true;
reject(e)
}
} else { //普通值
return resolve(x)
}
}
复制代码
Step4
到目前为止, promise的大框架基本完成. 接下来需要解决的两个小问题是值的穿透以及onFulfiled/onRejected的异步执行问题. 值的穿透的含义就是, 当then中使用没有任何方法, onFulfiled()中的data自动作为返回值. 实现起来也很简单, 在then的定义的最开始部分做一个判断:
Promise.prototype.then = function (onFulfiled, onRejected) {
//成功和失败默认不传, 给一个默认函数 可以实现值的穿透
onFulfiled = typeof onFulfiled === 'function'? onFulfiled:function(value) {
return value;
}
onRejected = typeof onRejected === 'function'? onRejected:function(err) {
throw err; //在值的穿透的情况下 应该走下一个then的onRejected而不是onFulfiled 保证逻辑的一致性
}
.....
}
复制代码
对于onFulfild/onRejected异步执行的问题, 则是在
if(self.status === 'resolved') {
promise2 = new Promise(function(resolve, reject){
let x = onFulfiled(self.value);
resolvePromise(promise2, x, resolve, reject)
...
}
}
复制代码
代码块使用setTimeout. 这时带来的副作用就是之前在promise构造函数中的代码块
try {
executor(resolve, reject);
} catch (e) {
reject(e);
}
复制代码
不能捕获到setTimeout中的异步函数, 因此需要在setTimeout中也需要包一层try/catch:
if(self.status === 'resolved') {
promise2 = new Promise(function(resolve, reject){
setTimeout(function(){ //用setTimeOut实现异步
try {
let x = onFulfiled(self.value); //x可能是普通值 也可能是一个promise, 还可能是别人的promise
resolvePromise(promise2, x, resolve, reject) //写一个方法统一处理
} catch (e) {
reject(e);
}
})
})
}
复制代码
step5
最终代码如下所示:
function Promise (executor) {
let self = this;
self.status = 'pending';
self.value = undefined;
self.reason = undefined;
self.onResolvedCallbacks = [];
self.onRejectedCallbacks = [];
function resolve(data_value) {
if(self.status === 'pending') {
self.status = 'resolved';
self.value = data_value;
self.onResolvedCallbacks.forEach(function(fn) {
fn();
})
}
}
function reject(data_reason) {
if(self.status === 'pending') {
self.status = 'rejected';
self.reason = data_reason;
self.onRejectedCallbacks.forEach(function(fn) {
fn();
})
}
}
try {
executor(resolve, reject);
} catch (e) {
reject(e);
}
}
function resolvePromise(promise2, x, resolve, reject) {
//有可能这里返回的x是别人的promise 要尽可能允许其他人乱写
if(promise2 === x) {//这里应该报一个循环引用的类型错误
return reject(new TypeError('循环引用'));
}
//看x是不是一个promise promise应该是一个对象
let called; //表示是否调用过成功或者失败
if (x!== null && (typeof x ==='object' ||typeof x === 'function')) {
//可能是promise 看这个对象中是否有then 如果有 姑且作为promise 用try catch防止报错
try {
let then = x.then;
if (typeof then === 'function') {
//成功
then.call(x, function(y) {
if (called) return //避免别人写的promise中既走resolve又走reject的情况
called = true;
resolvePromise(promise2, y, resolve, reject)
}, function(err) {
if (called) return
called = true;
reject(err);
})
} else {
resolve(x) //如果then不是函数 则把x作为返回值.
}
} catch (e) {
if (called) return
called = true;
reject(e)
}
} else { //普通值
return resolve(x)
}
}
Promise.prototype.then = function (onFulfiled, onRejected) {
//成功和失败默认不传给一个函数
onFulfiled = typeof onFulfiled === 'function'? onFulfiled:function(value) {
return value;
}
onRejected = typeof onRejected === 'function'? onRejected:function(err) {
throw err;
}
let self = this;
let promise2; //新增: 返回的promise
if(self.status === 'resolved') {
promise2 = new Promise(function(resolve, reject){
setTimeout(function(){ //用setTimeOut实现异步
try {
let x = onFulfiled(self.value); //x可能是普通值 也可能是一个promise, 还可能是别人的promise
resolvePromise(promise2, x, resolve, reject) //写一个方法统一处理
} catch (e) {
reject(e);
}
})
})
}
if(self.status === 'rejected') {
promise2 = new Promise(function(resolve, reject){
setTimeout (function() {
try {
let x = onRejected(self.reason);
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e);
}
})
})
}
if(self.status === 'pending') {
promise2 = new Promise (function(resolve, reject) {
self.onResolvedCallbacks.push(function(){
setTimeout(function(){
try {
let x = onFulfiled(self.value);
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e);
}
})
});
self.onRejectedCallbacks.push(function(){
setTimeout(function(){
try {
let x = onRejected(self.reason);
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e);
}
})
});
})
}
return promise2;
}
复制代码
使用promises-aplus-tests对该方法进行测试, 最终测试得到通过. 如图所示:
当然, 该promise相对于原生的promise还有一些不同,比如没有实现catch功能,没有静态方法等. 这些部分我们将下次进行详细讨论.