一、前言
- 其实 Promise 有多种规范,除了前面的 Promise A、promise A+ 还有 Promise/B,Promise/D。
- 目前我们使用的 Promise 是基于 Promise A+ 规范实现的,感兴趣的移步 Promise A+规范了解一下,这里不赘述。
1. Promise基本使用
const promise = new Promise((resolve, reject) => {
// 其他代码...
if (/* 异步操作成功 */){
resolve(value);
} else {
reject(error);
}
})
promise.then(value => {
console.log('resolve', value)
}, reason => {
console.log('reject', reason)
})
// 输出 resolve success
2. 思路
Promise 是一个类,在执行这个类的时候会传入一个执行器,这个执行器会立即执行
Promise 会有三种状态
- Pending 等待
- Fulfilled 完成
- Rejected 失败
状态只能由 Pending --> Fulfilled 或者 Pending --> Rejected,且一但发生改变便不可二次修改;
Promise 中使用 resolve 和 reject 两个函数来更改状态;
then 方法内部做但事情就是状态判断
- 如果状态是成功,调用成功回调函数
- 如果状态是失败,调用失败回调函数
二、实现同步版本
1. 传入执行器
-
executor
是实例化时传入的一个执行器,进入会立即执行。 -
在执行器中传入
resolve
和reject
方法,可以在外部进行调用。
class MyPromise {
constructor(executor){
// 调用此方法就是成功
let resolve = () => {}
// 调用此方法就是失败
let reject = () => {}
try {
// executor 执行器,传入resolve和reject方法
executor(resolve, reject)
}catch {
reject(error)
}
}
}
状态与结果的管理
// 先定义三个常量表示状态
var PENDING = 'pending';
var FULFILLED = 'fulfilled';
var REJECTED = 'rejected';
class MyPromise {
constructor(executor) {
// 初始化状态,为pending
this.status = PENDING;
// 结果/原因
this.value = undefined;
this.reason = undefined;
// 修改状态,并执行成功回调
let resolve = (value) => {
// 只有状态是等待,才执行状态修改
if (this.status === PENDING) {
// 状态修改为成功,并保存结果
this.status = FULFILLED;
this.value = value;
}
}
// 修改状态,并执行失败回调
let reject = (reason) => {
// 只有状态是等待,才执行状态修改
if (this.status === PENDING) {
// 状态修改为失败,保存失败原因
this.status = REJECTED;
this.reason = reason;
}
}
// 立即执行,将 resolve 和 reject 函数传给使用者
try {
executor(resolve, reject)
} catch (error) {
reject(error)
}
}
}
2. then 的简单实现
class MyPromise {
// 省略...
// then方法实现
then(onFulfilled, onRejected) {
if (this.status === FULFILLED) {
// 调用成功回调,并且把值返回
onFulfilled(this.value);
} else if (this.status === REJECTED) {
// 调用失败回调,并且把原因返回
onRejected(this.reason);
}
}
}
module.exports = MyPromise
到这里就可以处理简单的同步问题了。
测试
const MyPromise = require('./my-promise.js')
const promise = new MyPromise((resolve, reject) => {
resolve('success')
reject('err')
})
promise.then(value => {
console.log('resolve', value)
}, reason => {
console.log('reject', reason)
})
执行node test
,查看结果
三、加入异步逻辑
1. 缓存成功与失败回调
- 执行到
then
方法时,耗时操作还没有结果,这个时候先将回调保存起来。 - 这里使用数组保存,因为
then
方法可以调用多次。
class MyPromise {
constructor(executor) {
// 用于存储回调函数
this.onResolvedCallbacks = [];
this.onRejectedCallbacks = [];
}
// 执行回调
then(onFulfilled, onRejected) {
if (this.status === FULFILLED) {
// 调用成功回调,并且把值返回
onFulfilled(this.value);
} else if (this.status === REJECTED) {
// 调用失败回调,并且把原因返回
onRejected(this.reason);
} else if (this.status === PENDING) {
// 存储回调函数
this.onResolvedCallbacks.push(() => {
onFulfilled(this.value)
});
this.onRejectedCallbacks.push(() => {
onRejected(this.reason);
})
}
}
}
- 状态变更的时候触发
// 修改状态,并执行成功回调
let resolve = (value) => {
// 只有状态是等待,才执行状态修改
if (this.status === PENDING) {
// 状态修改为成功,并保存值
this.status = FULFILLED;
this.value = value;
// 执行回调
this.onResolvedCallbacks.forEach(fn => fn());
}
}
// 修改状态,并执行失败回调
let reject = (reason) => {
// 只有状态是等待,才执行状态修改
if (this.status === PENDING) {
// 状态修改为失败,并保存原因
this.status = REJECTED;
this.reason = reason;
// 执行回调
this.onRejectedCallbacks.forEach(fn => fn());
}
}
- 到这里,就可以处理异步问题了。
测试
- 使用定时器模拟耗时操作。
const MyPromise = require('./my-promise.js')
const promise = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve('success')
}, 2000);
})
promise.then(value => {
console.log(1)
console.log('resolve', value)
})
promise.then(value => {
console.log(2)
console.log('resolve', value)
})
promise.then(value => {
console.log(3)
console.log('resolve', value)
})
结果
1
resolve success
2
resolve success
3
resolve success
这里实现了一个发布订阅模式,收集依赖 => 触发通知 => 取出依赖执行
2. then 的链式调用&值穿透特性
then
方法要链式调用那么就需要返回一个Promise
对象。then
方法里面return
一个返回值作为下一个 then 方法的参数,这就是所谓的值的穿透。then
方法中抛出异常,需要把异常作为参数传递给下一个 then 的失败的回调中。
then(onFulfilled, onRejected) {
//解决 onFufilled,onRejected 没有传值的问题
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v;
//因为错误的值要让后面访问到,所以这里也要跑出个错误,不然会在之后 then 的 resolve 中捕获
onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err };
// 每次调用 then 都返回一个新的 promise
let promise2 = new Promise((resolve, reject) => {
if (this.status === FULFILLED) {
process.nextTick(() => { // 状态变化,添加微任务
try {
// 得到then方法的返回值
let x = onFulfilled(this.value);
resolve(x) // 先不考虑x为promise的情况
} catch (e) {
reject(e)
}
});
}
if (this.status === REJECTED) {
process.nextTick(() => {
try {
let x = onRejected(this.reason);
resolve(x) // 先不考虑x为promise的情况
} catch (e) {
reject(e)
}
});
}
if (this.status === PENDING) {
this.onResolvedCallbacks.push(() => {
process.nextTick(() => {
try {
let x = onFulfilled(this.value);
resolve(x) // 先不考虑x为promise的情况
} catch (e) {
reject(e)
}
});
});
this.onRejectedCallbacks.push(() => {
process.nextTick(() => {
try {
let x = onRejected(this.reason);
resolve(x) // 先不考虑x为promise的情况
} catch (e) {
reject(e)
}
});
});
}
})
return promise2
}
这里使用process.nextTick()
模拟异步
为什么 Promise
的 onFulfilled
和 onRejected
必须是异步的?
我们知道
Promise handler
执行时机其实很容易定义
- 如果父亲 Promise 的状态不是 pending, 就立马执行
- 否则, 等到父亲 Promise 状态变化时才执行
因此, 理论上 Promise handler 执行不管是异步还是同步都可以完成这个逻辑。
我们把强制异步的标准派当做正方, 把认为同步异步都可以的当做反方。
反方认为: 如果本来是同步的逻辑被强制异步执行, 肯定是会有性能损失, 标准不应该强制异步。
正方则认为: 流程可预测(predictably)是最为重要的, 标准需要强制异步。
最终, 正方胜利, 标准制定者认为 Promise 主要解决的问题是 流程抽象 和 规范一致性, 性能并不是首要考虑的。
我们希望代码执行顺序是完全可以预测的, 流程控制最首要的就是可控!
- 而回调(then方法中的)如果可能同步、也可能异步,执行顺序就变成了不确定的。
例子1
var foo
promise.then(() => {
foo = {}
})
console.log(foo.bar) // always crash
// 任何一个 Promise 使用者都应该知道这段代码是会报错的
例子2
console.log(1)
var promise = new Promise(resolve => {
console.log(2)
resolve()
})
promise.then(() => {
console.log(3)
})
console.log(4)
// 我们确保执行输出顺序永远是 `1, 2, 4, 3`
3. then返回值是一个Promise
我们继续完成Promise,再结合 Promise/A+ 规范梳理一下思路:
- 如果 then 的返回值 x 是一个 promise,那么会等这个 promise 执行完,promise 如果成功,就走下一个 then 的成功;如果失败,就走下一个 then 的失败;如果抛出异常,就走下一个 then 的失败;「规范 Promise/A+ 2.2.7.3、2.2.7.4」
- 如果 then 的返回值 x 和 promise 是同一个引用对象,造成循环引用,则抛出异常,把异常传递给下一个 then 的失败的回调中;「规范 Promise/A+ 2.3.1」
- 如果 then 的返回值 x 是一个 promise,且 x 同时调用 resolve 函数和 reject 函数,则第一次调用优先,其他所有调用被忽略;「规范 Promise/A+ 2.3.3.3.3」
const resolvePromise = (promise2, x, resolve, reject) => {
// 不允许自己引用自己,这样会陷入死循环
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
}
// 一个then方法防止调用多个操作
let called;
// x是一个对象或者函数,且不是null
if ((typeof x === 'object' && x != null) || typeof x === 'function') {
try {
// 为了判断 resolve 过的就不用再 reject 了(比如 reject 和 resolve 同时调用的时候)
let then = x.then;
// 如果then是函数,就默认是promise了
if (typeof then === 'function') {
// 就让then执行 第一个参数是this指向 后面是成功的回调 和 失败的回调
then.call(x, y => {
// 成功和失败只能调用一个
if (called) return;
called = true;
// resolve的结果依旧是promise 那就继续解析
resolvePromise(promise2, y, resolve, reject);
}, r => {
// 成功和失败只能调用一个
if (called) return;
called = true;
reject(r);
});
} else {
// 如果 x.then 是个普通值就直接返回 resolve 作为结果(能调用.then,但不是函数,说明不是promise,只是和promise类似)
resolve(x);
}
} catch (e) {
if (called) return;
called = true;
reject(e)
}
} else {
// 如果 x 是个普通值就直接返回 resolve 作为结果
resolve(x)
}
}
- then方法中引入该函数
x.then(resolve, reject) // 先不考虑x为promise的情况
// 修改为
resolvePromise(promise2, x, resolve, reject);
测试
const promise = new MyPromise((resolve, reject) => {
setTimeout(() => {
reject('失败');
}, 1000);
})
promise.then().then().then(data => {
console.log(data);
}, err => {
console.log('err', err);
})
打印结果
"失败 err"
- 至此,我们已经完成了 promise 最关键的部分:then 的链式调用和值的穿透。
- 搞清楚了 then 的链式调用和值的穿透,你也就搞清楚了 Promise。
四、测试 Promise 是否符合规范
- Promise/A+规范提供了一个专门的测试脚本,可以测试所编写的代码是否符合Promise/A+的规范。
- 首先,在 promise 实现的代码中,增加以下代码:
MyPromise.defer = MyPromise.deferred = function() {
let dfd = {}
dfd.promise = new MyPromise((resolve, reject) => {
dfd.resolve = resolve;
dfd.reject = reject;
});
return dfd;
}
下载依赖
yarn add promises-aplus-tests
添加命令
// package.json
"scripts": {
"test": "promises-aplus-tests my-promise.js"
}
执行yarn test
,查看结果
通过全部872 条测试用例。
五、Promise 的 API
Promise.resolve()
Promise.reject()
Promise.prototype.catch()
Promise.prototype.finally()
Promise.all()
Promise.race()
1. Promise.resolve
- 默认产生一个成功的 promise。
static resolve(data){
return new Promise((resolve,reject)=>{
resolve(data);
})
}
- 这里需要注意的是,promise.resolve 是具备等待功能的。(异步)
- 如果参数是 promise 会等待这个 promise 解析完毕,在向下执行。
let resolve = (value) => {
// ======新增逻辑======
// 如果 value 是一个promise,那我们的库中应该也要实现一个递归解析
if(value instanceof Promise){
// 递归解析
return value.then(resolve,reject)
}
// ===================
if(this.status === PENDING) {
this.status = FULFILLED;
this.value = value;
this.onResolvedCallbacks.forEach(fn=>fn());
}
}
2. Promise.reject
默认产生一个失败的 promise,Promise.reject 是直接将值变成错误结果。
static reject(reason){
return new Promise((resolve,reject)=>{
reject(reason);
})
}
3. Promise.prototype.catch
用来捕获 promise 的异常,就相当于一个没有成功的 then。
Promise.prototype.catch = function(errCallback){
return this.then(null,errCallback)
}
4. Promise.prototype.finally
不管成功还是失败都会执行。
Promise.prototype.finally = function(callback) {
return this.then((value)=>{
return Promise.resolve(callback()).then(()=>value)
},(reason)=>{
return Promise.resolve(callback()).then(()=>{throw reason})
})
}
5. Promise.all
Promise.all = function(values) {
if (!Array.isArray(values)) {
const type = typeof values;
return new TypeError(`TypeError: ${type} ${values} is not iterable`)
}
return new Promise((resolve, reject) => {
let resultArr = [];
let orderIndex = 0;
const processResultByKey = (value, index) => {
resultArr[index] = value;
if (++orderIndex === values.length) {
resolve(resultArr)
}
}
for (let i = 0; i < values.length; i++) {
let value = values[i];
if (value && typeof value.then === 'function') {
value.then((value) => {
processResultByKey(value, i);
}, reject);
} else {
processResultByKey(value, i);
}
}
});
}
6. Promise.race
用来处理多个请求,克隆最快的那一个,但是不会取消其他的promise。
Promise.race = function(promises) {
return new Promise((resolve, reject) => {
// 一起执行就是for循环
for (let i = 0; i < promises.length; i++) {
let val = promises[i];
if (val && typeof val.then === 'function') {
val.then(resolve, reject);
} else { // 普通值
resolve(val)
}
}
});
}
参考资料
https://github.com/qiruohan/article/blob/master/promise/promise.js