导读
上一篇我们讲解了Promise的使用,今天我们来通过Promise/A+ 规范来手写一个MyPromise类,废话不多说,让我们开始把。
规范定义了些什么
Promise状态
-
- promise 有且只有一种状态(pending, fulfilled, rejected三种状态中的其中一种)
-
- promise在pending状态下面可以转换成fulfilled状态 或者 rejected状态
-
- fulfilled状态下,不能再转换成其他状态,有且必须有一个值value, 不能被改变
-
- rejected状态下,不能再转换成其他状态,有且必须有一个原因reason, 不能被改变
then方法
-
- then方法用来获取转成一种状态下的value或者reason
-
- 接受两个参数,onFulfilled, onRejected(两个都是函数)
-
- 如果onFulfilled, onRejected不是函数,则必须忽略
-
- 如果onFulfilled是一个函数
- 必须在转换成状态fulfilled完成后调用,并且把value作为函数的第一个参数
- 不能在fulfilled状态完成之前调用
- 最多调用一次
-
- 如果onRejected是一个函数
- 必须在转换成状态rejected完成后调用,并且把reason作为函数的第一个参数
- 不能在rejected状态完成之前调用
- 最多调用一次
-
- onFulfilled和onRejected都是函数调用,所以this指向全局global, 严格模式undefined
-
- then方法可以多次被调用
- 当promise完成fulfilled状态时候,各个onFulfilled回调必须按照原始的then顺序来调用
- 当promise拒绝rejected状态时候,各个onRejected回调必须按照原始的then顺序来调用
-
- then必须返回一个新的promise对象
promise2 = promise1.then(onFulfilled, onRejected);
- 如果onFulfilled或者onRejected回调返回一个值x, 对x进行判断分析,请看
分析then返回的x值
- 如果onFulfilled或者onRejected 抛出一个异常,那么promise2必须是拒绝状态,并且异常信息作为拒绝的原因
- 如果onFulfilled不是一个函数,并且promise1已经是fulfilled状态,那么promise2则必须是和promise1一样的值来完成状态promise
- 如果onRejected不是一个函数,并且promise1已经是rejected状态,那么promise2则必须和promise1一样的原因来拒绝状态promise
- then必须返回一个新的promise对象
分析then返回的x值(resolvePromise )
-
- 如果promise与x引用的是同一个对象,则抛出TypeError作为拒绝原因的promise
-
- 如果x是一个promise对象
- 2.1 如果这个 x 在pending状态,则promise需要保持pending状态,直到x转换成fulfilled或者是rejected状态
- 2.2 如果这个 x 已经是fulfilled状态, 使用相同的值完成fulfilled promise
- 2.3 如果这个 x 已经是rejected状态, 使用相同的原因拒绝rejected promise
-
- 如果x是一个对象或者是函数类型
- 3.1 将then 设置为x.then
- 3.2 如果检测属性x.then 返回一个异常结果e, 则以e为原因reject promise
- 3.3 如果then是一个函数,用x作为this调用它,第一个参数resolvePromise,第二个参数rejectPromise
- 3.3.1 如果当resolvePromise的值为y时, 继续解析,运行resolvePromise
- 3.3.2 如果当rejectPromise以一个理由r调用,则直接以r为原因reject promise
- 3.3.3 如果同时调用了resolvePromise和rejectPromise,或者多次调用相同的参数,那么第一次调用优先,后续的调用将被忽略。
- 3.3.4 如果调用then抛出异常e
- 3.3.4.1 如果resolvePromise或者rejectPromise已经被调用,则忽略
- 3.3.4.2 或者以e为原因reject promise
- 3.4 如果then不是一个函数,用x完成(fulfill)promise
-
- 如果x不是一个对象或函数,用x完成(fulfill)promise
手写MyPromise
1. 基础版
// 定义三个状态常量
const PENDING = 'pending' // 待定状态
const FULFILLED = 'fulfilled' // 完成状态
const REJECTED = 'rejected' // 拒绝状态
class MyPromise {
// 接受一个函数executor
constructor(executor) {
// 初始化待定状态
this.status = PENDING
// 初始化value
this.value = undefined
// 初始化原因reason
this.reason = undefined
// 定义完成时,执行函数
let resolve = (value) => {
// 状态为 PENDING 时才可以更新状态,防止 executor 中调用了两次 resovle/reject 方法
if (this.status === PENDING) {
this.status = FULFILLED
this.value = value
}
}
// 定义拒绝时,执行函数
let reject = (reason) => {
// 状态为 PENDING 时才可以更新状态,防止 executor 中调用了两次 resovle/reject 方法
if (this.status === PENDING) {
this.status = REJECTED
this.reason = reason
}
}
// try/catch 捕捉异常,一点执行失败就reject promise
try {
executor(resolve, reject)
} catch (error) {
reject(error)
}
}
/**
* then 方法接受两个参数
* @param {成功回调} onFulfilled
* @param {失败回调} onRejected
*/
then (onFulfilled, onRejected) {
// 当状态更新成完成,执行成功回调onFulfilled,并将value作为第一个参数
if (this.status === FULFILLED) {
onFulfilled(this.value)
}
// 当状态更新成拒绝,执行失败回调onRejected,并将reason作为第一个参数
if (this.status === REJECTED) {
onRejected(this.reason)
}
}
}
复制代码
我们来一起测试一下:
const p1 = new MyPromise((resolve, reject) => {
resolve("ok");
}).then(
(value) => {
console.log("success", value);
},
(reason) => {
console.log("faild", reason);
}
);
复制代码
控制台输出:
'success ok'
复制代码
现在实现了一个基础版本,但是有一个问题就是executor
中异步执行resolve
或者reject
,就会出现什么反应都没有的情况,请看下面:
const p1 = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve("ok");
}, 1000);
}).then(
(value) => {
console.log("success", value);
},
(reason) => {
console.log("faild", reason);
}
);
复制代码
因为在执行then方法的时候,状态还处在pending,不是完成状态,所以没有执行。在这里我们就应该在pending状态的时候将完成的回调和拒绝的回调分别收集起来
,等到异步执行后,触发resolve或者reject, 然后依次执行回调 。 因为promise的回调是异步的(是微任务)暂且用setTimeout(宏任务)来实现下
, 我们来优化一下:
// 定义三个状态常量
const PENDING = 'pending' // 待定状态
const FULFILLED = 'fulfilled' // 完成状态
const REJECTED = 'rejected' // 拒绝状态
class MyPromise {
// 接受一个函数executor
constructor(executor) {
...
// 存放成功回调
this.onFulfilledCallbacks = []
// 存放失败回调
this.onRejectedCallbacks = []
// 定义完成时,执行函数
let resolve = (value) => {
// 状态为 PENDING 时才可以更新状态,防止 executor 中调用了两次 resovle/reject 方法
if (this.status === PENDING) {
this.status = FULFILLED
this.value = value
// 更新完成状态,依次执行成功回调
this.onFulfilledCallbacks.forEach(fn=>fn())
}
}
// 定义拒绝时,执行函数
let reject = (reason) => {
// 状态为 PENDING 时才可以更新状态,防止 executor 中调用了两次 resovle/reject 方法
if (this.status === PENDING) {
this.status = REJECTED
this.reason = reason
// 更新拒绝状态后,依次执行成功回调
this.onRejectedCallbacks.forEach(fn=>fn())
}
}
// try/catch 捕捉异常,一点执行失败就reject promise
try {
executor(resolve, reject)
} catch (error) {
reject(error)
}
}
/**
* then 方法接受两个参数
* @param {成功回调} onFulfilled
* @param {失败回调} onRejected
*/
then (onFulfilled, onRejected) {
...
// 处于pending状态 收集回调
if (this.status === PENDING) {
// 收集成功回调
this.onFulfilledCallbacks.push(() => {
setTimeout(() => {
onFulfilled(this.value)
},0)
})
// 收集失败回调
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
onRejected(this.reason)
},0)
})
}
}
}
复制代码
2. then 方法的两个参数onFulfilled和onRejected
根据上述规范定义then方法中的参数,需要对onFulfilled和onRejected进行判断
/**
* then 方法接受两个参数
* @param {成功回调} onFulfilled
* @param {失败回调} onRejected
*/
then (onFulfilled, onRejected) {
// 判断onFulfilled,onRejected
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason }
....
}
复制代码
3. then 方法的链式调用
then方法链式调用new Promise((resolve,reject)=>{}).then().then()
,then会返回一个新的promise,规范中有这么说的:
如果 onFulfilled 或者 onRejected 返回一个值 x ,则运行下面的 Promise 解决过程:[[Resolve]](promise2, x)
Promise 解决过程是一个抽象的操作,其需输入一个 promise 和一个值,再加之我们拿到x值后还需要处理,所以总共传入4个参数。我们将这个解决过程起名resolvePromise,先将它抽象出来,随后将刚才考虑到的情况及别的情况在里面处理,onFulfilled 或者 onRejected也有可能会执行错误所以用try/catch捕捉异常,所以修改如下:
// 定义三个状态常量
const PENDING = "pending"; // 待定状态
const FULFILLED = "fulfilled"; // 完成状态
const REJECTED = "rejected"; // 拒绝状态
class MyPromise {
...
/**
* then 方法接受两个参数
* @param {成功回调} onFulfilled
* @param {失败回调} onRejected
*/
then(onFulfilled, onRejected) {
// 判断onFulfilled,onRejected
onFulfilled =
typeof onFulfilled === "function" ? onFulfilled : (value) => value;
onRejected =
typeof onRejected === "function"
? onRejected
: (reason) => {
throw reason;
};
let promise2 = new MyPromise((resolve, reject) => {
// 当状态更新成完成,执行成功回调onFulfilled,并将value作为第一个参数
if (this.status === FULFILLED) {
setTimeout(() => {
try {
let x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error);
}
}, 0);
}
// 当状态更新成拒绝,执行失败回调onRejected,并将reason作为第一个参数
if (this.status === REJECTED) {
setTimeout(() => {
try {
let x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error);
}
}, 0);
}
// 处于pending状态 收集回调
if (this.status === PENDING) {
// 收集成功回调
this.onFulfilledCallbacks.push(() => {
setTimeout(() => {
try {
let x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error);
}
}, 0);
});
// 收集失败回调
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
let x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error);
}
}, 0);
});
}
});
return promise2;
}
}
复制代码
3.1 Promise 解决过程 resolvePromise 函数
function resolvePromise (promise2, x, resolve, reject) {
// 1. 如果promise与x引用的是同一个对象,则抛出TypeError作为拒绝原因的promise
if (x === promise2) {
return reject(new TypeError('Chaining cycle detected for promise #<Promise>'));
}
// 防止多次调用
let called;
// 如果x是一个对象(包含promise)或者是一个函数, 如果是null用x完成(fulfill)promise
if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
try {
let then = x.then
// then是函数,默认x是promise
if (typeof then === 'function') {
then.call(x, y => {
if (called) return
called = true
// 继续判断y值是否是promise, 递归resolvePromise
resolvePromise(promise2, y, resolve, reject)
}, reason => {
if (called) return
called = true
reject(reason)
})
} else {
resolve(x)
}
} catch (error) {
if (called) return
called = true
reject(error)
}
} else {
resolve(x)
}
}
复制代码
4. 测试MyPromise
1、npm 有一个promises-aplus-tests插件 npm i promises-aplus-tests -D
2、命令行 promises-aplus-tests [js文件名] 即可验证
新建testMyPromise.js
const MyPromise = require('./MyPromise')
// 用于promises-aplus-tests 测试定义的方法
MyPromise.deferred = function () {
var result = {};
result.promise = new MyPromise(function (resolve, reject) {
result.resolve = resolve;
result.reject = reject;
});
return result;
}
module.exports = MyPromise
复制代码
修改package.json
"scripts": {
"test": "promises-aplus-tests testMyPromise"
},
复制代码
执行命令
npm run test
复制代码
5. 原型方法catch 和 finally
5.1 catch
.catch() 其实只是没有给 onFulfilled 预留参数位置的 .then() 而已。同样也返回一个promise
catch (onRejected) {
return this.then(null, onRejected)
}
复制代码
5.2 finally
无论结果是fulfilled或者是rejected,都会执行指定的回调函数, 同样也返回一个promise
finally (callback) {
// 构造函数
let p = this.constructor
return p.then(value => {
return p.resolve(callback()).then(()=>value)
}, reason => {
return p.resolve(callback()).then(()=>{throw reason})
})
}
复制代码
6. 静态方法
6.1 resolve
静态方法 resolve返回一个解析过的Promise对象。如果传入的值是一个promise,就要递归解析,所以这里我们还要改一处代码就是 constructor 中的resolve方法,请看:
// 定义完成时,执行函数
let resolve = (value) => {
// ======新增逻辑======
// 如果 value 是一个promise,那我们的库中应该也要实现一个递归解析
if(value instanceof MyPromise){
// 递归解析
return value.then(resolve,reject)
}
};
复制代码
resolve方法
// 静态方法 Promise.resolve返回一个解析过的Promise对象。
static resolve (value) {
return new MyPromise((resolve, reject) => {
resolve(value)
})
}
复制代码
6.2 reject()
静态方法reject, 返回一个带有拒绝原因的Promise对象。
static reject (reason) {
return new MyPromise((resolve, reject) => {
reject(reason)
})
}
复制代码
6.3 all()
all() 方法接收一个promise的iterable类型(Array, Map, Set),只返回一个promise实例,onFulfilled回调的是所有promise实例的完成状态结果数组作为参数。onRejected回调的结果是只要其中一个拒绝就返回当前拒绝的原因作为参数
// 静态方法all,
static all (iterable) {
let results = [] // 存放完成结果
let index = 0 // 当前处理索引值
return new MyPromise((resolve, reject) => {
// 判断是否是可迭代对象
if (iterable[Symbol.iterator]().next().done) {
return resolve([])
}
for (const item of iterable) {
// 使用resolve方法,无论是promise对象还是值,都会被包装成一个promise对象
MyPromise.resolve(item).then(value => {
results[index] = value
index++
if (index === iterable.length) {
resolve(results)
}
}, reason => {
reject(reason)
})
}
})
}
复制代码
6.4 race()
方法返回一个 promise,一旦迭代器中的某个promise解决或拒绝,返回的 promise就会解决或拒绝。它可以是完成( resolves),也可以是失败(rejects),这要取决于第一个完成的方式是两个中的哪个
// 静态方法race()
static race (iterable) {
return new MyPromise((resolve, reject) => {
for (const item of iterable) {
MyPromise.resolve(item).then(value => {
resolve(value)
}, reason => {
reject(reason)
})
}
})
}
复制代码
6.5 allSettled()
allSettled()同样接受一个promise的iterable类型(Array, Map, Set),只返回一个promise实例,只有等到所有的promise实例都返回结果才结束,返回的promise实例对象只有一个fulfilled状态,返回的结果是一个对象数组,对象中存储着promise的状态,用键值status,如果是fulfilled状态用value键值储存返回值,如果是rejected状态,用reason键值储存原因
static allSettled (iterable) {
let results = [] // 存放完成结果
let index = 0 // 当前处理索引值
return new MyPromise((resolve, reject) => {
// 判断是否是可迭代对象
if (iterable[Symbol.iterator]().next().done) {
return resolve([])
}
for (const item of iterable) {
// 使用resolve方法,无论是promise对象还是值,都会被包装成一个promise对象
MyPromise.resolve(item).then(value => {
results[index] = {
status: 'fulfilled',
vaule: value
}
index++
if (index === iterable.length) {
resolve(results)
}
}, reason => {
results[index] = {
status: 'rejected',
reason: reason
}
index++
if (index === iterable.length) {
resolve(results)
}
})
}
})
}
复制代码