一、前言
Promise现在已经被使用在日常需求的各个异步场景中,本篇文章主要通过探索其规范和解析流程,以手写实现的形式进一步理解Promise的工作原理。
二、基本术语
Promise:表示一个拥有then方法的对象或者函数,其行为符合规范
thenable:表示定义在then方法上的对象或者函数
value:表示任何合法JavaScript值(包括undefined,thenable,或者promise)
reason:表示一个表明promise为什么失败的值
exception:表示使用throw抛出的值
三、规范
1. Promise状态
-
pending:
- 只可变为fulfilled或者rejected之一,不可逆
-
fulfilled:
状态不可再改变
必须有一个value,且不可变
-
rejected:
状态不可再改变
必须有一个reason(拒绝的原因),且不可变
2. then方法
首先我们要明确的是,一个promise必须提供一个then方法,该方法可以支持访问其状态转变后的value和reason,其接收两个参数promise.then(onFulfilled, onRejected)
2.1 onFulfilled和onRejected都是可选的参数
2.1.1 如果onFulfilled不是函数,必须被忽略
2.1.2 如果onRejected 不是函数,必须被忽略
2.2 如果onFulfilled是一个函数
2.2.1 它必须在promise由pending转变为fulfilled状态后被调用,然后使用promise的值作为它的第一个参数
2.2.2 它不能在promise转变为fulfilled前被调用
2.2.3 它最多只能被调用1次
2.3 如果onRejected是一个函数
2.3.1 它必须在promise由pending转变为rejected状态后被调用,然后使用promise的值作为它的第一个参数
2.3.2 它不能在promise转变为rejected前被调用
2.3.3 它最多只能被调用1次
2.4 onFulfilled 和 onRejected 只允许在execution context 栈仅包含平台代码时运行
2.5 onFulfilled 和 onRejected 必须被当做函数调用 (i.e. 即函数体内的 this 为undefined)
2.6 对于同一个promise,它的then方法可以被调用多次
2.6.1 当一个promise成功后,所有onFulfilled都必须按照其注册顺序执行
2.6.2 当一个promise失败后,所有onRejected都必须按照其注册顺序执行
2.7 then方法必须返回一个promise
2.7.1 如果onFulfilled或onRejected 返回了值x, 则执行Promise 解析流程[[Resolve]](promise2, x)
2.7.2 如果onFulfilled 或 onRejected抛出了异常e,则promise2应当以e为reason被拒绝。
2.7.3 如果 onFulfilled 不是一个函数且promise1已经完成状态,则promise2必须使用promise1的值完成.
2.7.4 如果 OnReject不是一个函数且promise1已经失败状态, 则promise2必须以相同的reason被拒绝.
四、解析流程
Promise的解析过程其实是以一个输入promise和value的抽象过程,可抽象的表示为[[Resolve]](promise, x)。
其中,如果x是thenable,并且x的行为有点像promise,它会尝试让promise采用x的状态。否则,它将使用x值来完成promise。
解析过程:
- 如果promise和x引用同一个对象,则用TypeError作为reason拒绝promise
let promise = Promise.resolve('done').then(() => {
return promise;
});
promise.then(null, (reason) => {
console.log('error', reason);
});
// error TypeError: Chaining cycle detected for promise #
- 如果x是一个promise,则采用它的状态
- 2.1 如果x为pending状态,promise必须保持pending状态直到x成功或者失败
let promise = Promise.resolve('done').then(() => {
return new Promise((resolve, reject) => {
setTimeout(function () {
resolve(5000);
}, 5000);
});
});
promise.then(
value => {
console.log('resolve', value);
},
reason => {
console.log('reject', reason);
}
);
// resolve 5000
- 2.2 如果x为完成状态,用相同的值完成promise
let promise = Promise.resolve('done').then(() => {
return Promise.resolve('success');
});
promise.then(
value => {
console.log('resolve', value);
},
reason => {
console.log('reject', reason);
}
);
// resolve success
- 2.3 如果x为失败状态,用相同的reason拒绝promise
let promise = Promise.resolve('done').then(() => {
return Promise.reject('fail');
});
promise.then(
value => {
console.log('resolve', value);
},
reason => {
console.log('reject', reason);
}
);
// reject fail
- 如果x是一个对象或函数
3.1 让then为x.then
3.2 如果检索属性x.then的结果抛出一个异常e,用e作为reason拒绝promise
-
3.3 如果then是一个函数,以x为this调用then函数, 且第一个参数是resolvePromise,第二个参数是rejectPromise,且:
3.3.1 如果resolvePromise被y值调用,运行[[Resolve]](promise, y)
3.3.2 如果rejectPromise被为r的reason调用,用r来拒绝promise
3.3.3 如果 resolvePromise和rejectPromise都被调用,或者对同一个参数进行多次调用,则第一次调用优先,任何进一步的调用被忽略
3.3.4 如果then不是函数,用x来完成promise
- 如果x不是对象或者函数,用x来完成promise
此处测试例子较为复杂参考github:点击查看900多行测试例子
五、手写实现一个简单Promise
上述我们对Promise的规范以及解析流程进行了梳理,接下来我们一起来实现一个简单的Promise,我们尚且命名为Promise_以区分Promise。
1. exector函数
exector函数是在创建一个Promise实例时会被立即执行,其接收两个参数resolve和reject
class Promise_ {
constructor(exector) {
this.status = 'pending'; // 初始状态
this.value = undefined; // 初始结果
this.onResolvedCallbacks = []; // 存储了所有fullfilled回调
this.onRejectedCallbacks = []; // 存储了所有rejected回调
let change = (status, value) => { // resolve和reject逻辑统一封装
if (this.status !== 'pending') return; // 只允许状态为pending向fullfilled和rejected转变
this.status = status;
this.value = value;
let fnArr = status === 'resolved' ? this.onResolvedCallbacks : this.onRejectedCallbacks;
fnArr.forEach(fn => { // then方法执行链
if (typeof fn !== 'function') return;
fn(this.value);
})
}
let resolve = result => { // 成功状态执行
if (this.onResolvedCallbacks.length > 0) { // 如果当前成功回调为空,使用setTimeout将本次change事件分发为宏任务
change("resolved", result);
}
let timer = setTimeout(() => { // 利用事件循环机制,先进行then方法成功和失败事件注册,以待下一次宏任务时实现链式调用
change("resolved", result);
clearTimeout(timer);
}, 0)
}
let reject = reason => { // 失败状态执行
if (this.onRejectedCallbacks.length > 0) {
change("rejected", reason);
}
let timer = setTimeout(() => { // 同上
change("rejected", reason);
clearTimeout(timer);
}, 0)
}
try {
exector(resolve, reject);
} catch (err) { // 异常都会导致本次状态变为rejected
reject(err);
}
}
}
通过一个测试案例验证一下状态变更逻辑
let resolvePromise = new Promise_((resolve, reject) => {
resolve('success');
})
let rejectPromise = new Promise_((resolve, reject) => {
reject('fail');
})
console.log(resolvePromise, rejectPromise);
2. then方法
then方法是我们的关键方法,其主要有两个参数,resolve为成功时的回调,reject为失败时的回调。
then方法返回的是一个新的Promise实例,以继续支持then的链式调用,且当resolve和reject不是一个function时,能够继续执行then链,并将value或者reason进行传递。
class Promise_ {
constructor(exector) {
// ...
}
then(resolveCallback, rejectCallback) {
if (typeof resolveCallback !== 'function') { // 如果成功回调不是一个方法,传递它的值
resolveCallback = result => {
return Promise_.resolve(result);
}
}
if (typeof rejectCallback !== 'function') { // 同上
rejectCallback = reason => {
return Promise_.reject(reason);
}
}
return new Promise_((resolve, reject) => { // then返回的是一个新的promise,其状态为pending
this.onResolvedCallbacks.push(result => { // 此处为箭头函数,this指向上一个promise,往上面一个成功回调添加事件
try {
let x = resolveCallback(result);
if (x instanceof Promise_) { // 如果x是一个promise实例,继续执行then
x.then(resolve, reject);
return;
}
resolve(x);
} catch (err) {
reject(err);
}
})
this.onRejectedCallbacks.push(reason => { // 同上
try {
let x = rejectCallback(reason);
if (x instanceof Promise_) {
x.then(resolve, reject);
return;
}
resolve(x); // 注意此处!!!上一个promise的reject并不会影响到下一个promise的resolve
} catch (err) {
reject(err);
}
})
})
}
}
编写一个简单例子测试上述功能
new Promise_((resolve, reject) => {
resolve('success')
reject('fail')
}).then(
result => { console.log('resolve', result) },
reason => { console.log('reject', reason) }
)
new Promise_((resolve, reject) => {
reject('fail')
resolve('success')
}).then(
result => { console.log('resolve', result) },
reason => { console.log('reject', reason) }
)
new Promise_((resolve, reject) => {
reject('fail')
resolve('success')
}).then(
result => { console.log('resolve', result); },
reason => reason
).then(
result => { console.log('resolve', result); },
reason => { console.log('reject', reason) }
)
结果如下:
3. resolve方法
Promise还支持以Promise.resolve(value)的形式,并且其返回的是一个Promise,支持then链式调用
static resolve(result) {
return new Promise_(resolve => {
resolve(result);
})
}
测试代码
Promise_
.resolve('done')
.then(
result => { console.log('resolve', result); },
reason => { console.log('reject', reason) }
)
4. reject方法
该方法原理同resolve方法
static reject(reason) {
return new Promise_((resolve, reject) => {
reject(reason);
})
}
5. catch方法
catch方法能够捕获到异常代码,并且返回的是一个Promise,通过then方法的第二个参数进行reject
catch(rejectCallback) {
return this.then(null, rejectCallback);
}
简单测试案例:
Promise_
.reject('fail')
.catch(err => {
console.log('err:', err)
})
Promise_
.resolve('success')
.then(result => {
throw new Error('fail')
})
.catch(err => {
console.log('err:', err)
})
结果:
6. finally方法
finally方法是在成功和失败的场景下都会执行的方法
finally(finallyCallback) {
return this.then(
value => this.constructor.resolve(finallyCallback()).then(() => value),
reason => this.constructor.reject(finallyCallback()).then(() => reason)
)
}
测试功能:
Promise_
.resolve('done')
.then(res => {
console.log('resolve', res)
})
.finally(() => {
console.log('finnally')
})
7. all方法
all方法传入一个promise array参数,其规则为只有当所有的promise都状态为完成时,才会返回一个结果数组
static all(promiseList) {
return new Promise_((resolve, reject) => {
let index = 0,
results = [],
promiseListLen = promiseList.length;
for (let i = 0; i < promiseListLen; i++) {
let promise = promiseList[i];
if (!(promise instanceof Promise_)) return;
promise
.then(result => {
index++;
results[i] = result;
if (index === promiseListLen) resolve(results);
})
.catch(reason => {
reject(reason);
})
}
})
}
编写一个简单测试案例:
let promiseList = [
Promise_.resolve(1),
new Promise_((resolve, reject) => {
setTimeout(() => {
resolve(2)
}, 5000)
})
]
Promise_
.all(promiseList)
.then(
result => { console.log('resolve', result); },
reason => { console.log('reject', reason) }
)
等待超过5秒钟以后得到结果:
8. race方法
race方法的参数和all方法一致,不同在于race方法是返回第一个状态为完成的promise的结果
static race(promiseList) {
return new Promise_((resolve, reject) => {
for (let i = 0; i < promiseList.length; i++) {
let promise = promiseList[i];
if (!(promise instanceof Promise_)) return;
promise
.then(result => {
resolve(result)
})
.catch(err => {
reject(err)
})
}
})
}
简单测试代码:
let promiseList = [
new Promise_((resolve, reject) => {
setTimeout(() => {
resolve(1)
}, 5000)
}),
new Promise_((resolve, reject) => {
setTimeout(() => {
resolve(2)
}, 1000)
})
]
Promise_
.race(promiseList)
.then(
result => { console.log('resolve', result); },
reason => { console.log('reject', reason) }
)
以上通过对Promise的规范和解析流程进行了分析,通过实现部分Promise的api帮助我们进一步理解Promise的工作原理。当然了,这个Promise_肯定不是完美的,实际上Promise内部自己实现了一个事件循环,并且在各端表现上事件循环还有一些差异。