最近学习了PromiseA+规范和手写Promise,记录一下并分享给有需要的各位兄弟姐妹。接下来我们一起来学习一下promiseA+规范。
PromiseA+规范
了解PromiseA+规范之前我们得知道Promise到底是什么?
- promise是什么?
- promise 是异步编程的一种思想,可以让异步的任务状态能够更清晰的管理,以及异步任务执行有了清晰的顺序,并且通过.then方法获取resolve或者reject的状态。
- promise 是一个有then方法的对象或函数,行为遵循promiseA+规范 接下来我们来了解一下PromiseA+规范
术语
- promise 是一个有then方法的对象或者是函数,行为遵循本规范
- thenable 是一个有then方法的对象或者是函数
- value 是promise状态成功时的值,也就是resolve的参数, 包括各种数据类型, 也包括undefined/thenable或者是 promise
- reason 是promise状态失败时的值, 也就是reject的参数, 表示拒绝的原因
- exception 是一个使用throw抛出的异常值
规范
接下来分几部分来看PromiseA+规范.
Promise状态
- pending 初始态,一个promise在resolve或者reject之前都是这个状态
- fulfilled 最终态,一个promise在resolve之后的状态(不可变),必须有一个value值
- rejected 最终态,一个promise在reject之后的状态(不可变),必须有一个reason值
Tip:即 pending -> resolve(value) -> fulfilled pending -> reject(reason) -> rejected
then
- promise一个提供一个then方法来访问最终的结果,无论是当前值(value)或据因(reason)
- promise.then(onFulfilled, onRejected)
-
onFulfilled和onRejected必须是函数类型,如果不是则被忽略
-
在promise变成 fulfilled(rejected) 时,应该调用 onFulfilled(onRejected), 参数是value(reason)
-
promise状态变成最终态之前onFulfilled, onRejected都不应该被调用
-
onFulfilled, onRejected都只能被调用一次
- then方法可以被调用多次
-
promise状态变成 fulfilled 后,所有的 onFulfilled 回调都需要按照then的顺序执行, 也就是按照注册顺序执行(所以在实现的时候需要一个数组来存放多个onFulfilled的回调)
-
promise状态变成 rejected 后,所有的 onRejected 回调都需要按照then的顺序执行, 也就是按照注册顺序执行(所以在实现的时候需要一个数组来存放多个onRejected的回调)
- 返回值 then 应该返回一个promise
promise2 = promise1.then(onFulfilled, onRejected);
-
onFulfilled 或 onRejected 执行的结果为x, 调用 resolvePromise(这个解决过程需要用到下方的resolvePromise来写)
-
如果 onFulfilled 或者 onRejected 执行时抛出异常e, promise2需要被reject
-
如果 onFulfilled 不是一个函数, promise2 以promise1的value 触发fulfilled
-
如果 onRejected 不是一个函数, promise2 以promise1的reason 触发rejected
resolvePromise
resolvePromise(promise2, x, resolve, reject)
- 如果 promise2 和 x 相等,那么 reject TypeError
- 如果 x 是一个 promsie
-
如果x是pending态,那么promise必须要在pending,直到 x 变成 fulfilled or rejected.
-
如果 x 被 fulfilled, fulfill promise with the same value.
-
如果 x 被 rejected, reject promise with the same reason.
- 如果 x 是一个 object 或者 是一个 function let then = x.then.
-
如果 x.then 这步出错,那么 reject promise with e as the reason.
-
如果 then 是一个函数,then.call(x, resolvePromiseFn, rejectPromise) resolvePromiseFn 的 入参是 y, 执行 resolvePromise(promise2, y, resolve, reject); rejectPromise 的 入参是 r, reject promise with r. 如果 resolvePromise 和 rejectPromise 都调用了,那么第一个调用优先,后面的调用忽略。
-
如果调用then抛出异常e 如果 resolvePromise 或 rejectPromise 已经被调用,那么忽略 则,reject promise with e as the reason
-
如果 then 不是一个function. fulfill promise with x.
规范了解完了开始手写Promise
这里用es6中的class来实现Promise
- 定义一个class来实现promise
class MPromise {
constructor() {
}
}
- 定义三种状态类型
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
- 初始化状态、value值、reason值
class MPromise {
constructor() {
// 初始状态为pending
this.status = PENDING;
this.value = null;
this.reason = null;
}
}
- resolve 和 reject 方法 根据刚才的规范, 这两个方法是要更改promise状态的, 从pending改到fulfilled/rejected.当状态变成fulfilled或rejected就不可再更改
resolve(value) {
if (this.status === PENDING) {
this.value = value;
this.status = FULFILLED;
}
}
reject(reason) {
if (this.status === PENDING) {
this.reason = reason;
this.status = REJECTED;
}
}
}
- 在promise中我们还需要一个入参,入参是一个函数,该函数接收resolve和reject两个参数,注意在初始化promise的时候, 就要执行这个函数, 并且有任何报错都要通过reject抛出去
class MPromise {
constructor(fn) {
// 初始状态为pending
this.status = PENDING;
this.value = null;
this.reason = null;
//入参初始化执行
try {
fn(this.resolve.bind(this), this.reject.bind(this));
} catch (e) {
this.reject(e);
}
}
- 接下来是关键的then方法(如有错误请大佬指点)
-
- then接收两个参数, onFulfilled 和 onRejected
then(onFulfilled, onRejected) {}
-
- 检查并处理参数, 之前提到的onFulfilled 和 onRejected如果不是function, 就忽略(指的是原样返回value或者reason).
isFunction(param) {
return typeof param === 'function';
}
//判断是否是function
then(onFulfilled, onRejected) {
const realOnFulfilled = this.isFunction(onFulfilled) ? onFulfilled : (value) => {//值的穿透
return value
}
const realOnRejected = this.isFunction(onRejected) ? onRejected : (reason) => {
throw reason;
};
}
- 3.根据当前promise的状态, 调用不同的函数。 但是需要注意的是:在then函数被调用的瞬间就会执行. 那这时候如果status还没变成fulfilled或者rejected. 所以我们需要一个状态的监听机制, 当状态变成fulfilled或者rejected后, 再去执行callback.
class MPromise {
constructor() {
FULFILLED_CALLBACK_LIST = [];
REJECTED_CALLBACK_LIST = [];
_status = PENDING;
...[省略之前代码]
then(onFulfilled, onRejected) {
const realOnFulfilled = this.isFunction(onFulfilled) ? onFulfilled : (value) => {
return value
}
const realOnRejected = this.isFunction(onRejected) ? onRejected : (reason) => {
throw reason;
};
const promise2 = new MPromise((resolve, reject) => {
switch (this.status) {
case FULFILLED: {
realOnFulfilled()
break;
}
case REJECTED: {
realOnRejected()
break;
}
case PENDING: {
this.FULFILLED_CALLBACK_LIST.push(realOnFulfilled)
this.REJECTED_CALLBACK_LIST.push(realOnRejected)
}
}
})
return promise2
}
}
}
-
4.在status发生变化的时候, 需要执行所有的回调. 这里咱们用一下es6的getter和setter.
_status = PENDING; //设置一个_state存储当前状态,防止陷入死循环 get status() { return this._status; } set status(newStatus) { this._status = newStatus; switch (newStatus) { case FULFILLED: { this.FULFILLED_CALLBACK_LIST.forEach(callback => { callback(this.value); }); break; } case REJECTED: { this.REJECTED_CALLBACK_LIST.forEach(callback => { callback(this.reason); }); break; } } }
-
5.then的返回值 5.1. then的返回值是一个Promise,所以我们需要返回promise。如果 onFulfilled 或者 onRejected 抛出一个异常 e ,则 promise2 必须拒绝执行,并返回拒因 e。这样的话, 我们就需要手动catch代码,遇到报错就reject。
then(onFulfilled, onRejected) {
const realOnFulfilled = this.isFunction(onFulfilled) ? onFulfilled : (value) => {
return value
}
const realOnRejected = this.isFunction(onRejected) ? onRejected : (reason) => {
throw reason;
};
//包装两个tryCatch函数
const promise2 = new MPromise((resolve, reject) => {
const fulfilledMicrotask = () => {
try {
realOnFulfilled(this.value);
} catch (e) {
reject(e)
}
};
const rejectedMicrotask = () => {
try {
realOnRejected(this.reason);
} catch (e) {
reject(e);
}
}
//根据status返回一个promise
switch (this.status) {
case FULFILLED: {
fulfilledMicrotask()
break;
}
case REJECTED: {
rejectedMicrotask()
break;
}
case PENDING: {
this.FULFILLED_CALLBACK_LIST.push(fulfilledMicrotask)
this.REJECTED_CALLBACK_LIST.push(rejectedMicrotask)
}
}
})
return promise2
}
5.2 如果 onFulfilled 不是函数且 promise1 成功执行, promise2 必须成功执行并返回相同的值
5.3 如果 onRejected 不是函数且 promise1 拒绝执行, promise2 必须拒绝执行并返回相同的据因。 需要注意的是,如果promise1的onRejected执行成功了,promise2应该被resolve.
const realOnFulfilled = this.isFunction(onFulfilled) ? onFulfilled : (value) => {
return value
}
const realOnRejected = this.isFunction(onRejected) ? onRejected : (reason) => {
throw reason;
};
5.4 如果 onFulfilled 或者 onRejected 返回一个值 x ,则运行resolvePromise方法
resolvePromise(promise2, x, resolve, reject)
resolvePromise
resolvePromise(promise2, x, resolve, reject) {
// 如果 newPromise 和 x 指向同一对象,以 TypeError 为据因拒绝执行 newPromise
// 这是为了防止死循环
if (promise2 === x) {
return reject(new TypeError('The promise and the return value are the same'));
}
if (x instanceof MPromise) {
// 如果 x 为 Promise ,则使 newPromise 接受 x 的状态
// 也就是继续执行x,如果执行的时候拿到一个y,还要继续解析y
queueMicrotask(() => {
//onFulfilled 和 onRejected 是微任务,咱们可以用queueMicrotask包裹执行函数
x.then((y) => {
this.resolvePromise(promise2, y, resolve, reject);
}, reject);
})
} else if (typeof x === 'object' || this.isFunction(x)) {
// 如果 x 为对象或者函数
if (x === null) {
// null也会被判断为对象
return resolve(x);
}
let then = null;
try {
// 把 x.then 赋值给 then
then = x.then;
} catch (error) {
// 如果取 x.then 的值时抛出错误 e ,则以 e 为据因拒绝 promise
return reject(error);
}
// 如果 then 是函数
if (this.isFunction(then)) {
let called = false;
// 将 x 作为函数的作用域 this 调用
// 传递两个回调函数作为参数,第一个参数叫做 resolvePromise ,第二个参数叫做 rejectPromise
try {
then.call(
x,
// 如果 resolvePromise 以值 y 为参数被调用,则运行 resolvePromise
(y) => {
// 需要有一个变量called来保证只调用一次.
if (called) return;
called = true;
this.resolvePromise(promise2, y, resolve, reject);
},
// 如果 rejectPromise 以据因 r 为参数被调用,则以据因 r 拒绝 promise
(r) => {
if (called) return;
called = true;
reject(r);
});
} catch (error) {
// 如果调用 then 方法抛出了异常 e:
if (called) return;
// 否则以 e 为据因拒绝 promise
reject(error);
}
} else {
// 如果 then 不是函数,以 x 为参数执行 promise
resolve(x);
}
} else {
// 如果 x 不为对象或者函数,以 x 为参数执行 promise
resolve(x);
}
}
测试一下
const test = new MPromise((resolve, reject) => {
setTimeout(() => {
reject(111);
}, 1000);
}).then((value) => {
console.log('then');
}).catch((reason) => {
console.log('catch'); //catch
})