遵循Promises/A+规范,实现Promise原生编写(上)
春节不停更,此文正在参加「星光计划-春节更帖活动」
前言
目前前端开发中异步编程越来越重要,我们不止需要会使用 promise
,更需要懂 promise
原理。
因此本文章将沿着 Promises/A+
规范的思路,一步一步的原生封装 promise
。
基础铺垫
promise
是异步编程的一种解决方案,广泛用在日常编程中。目前我们使用的 Promise
是基于 Promise A+
规范实现的,因此本文的主旋律也是沿着 promise A+
规范实现。
promise
必定处于下列三种状态之一:
Pending
等待态:promise
的初始状态Fulfilled
完成态: 代表promise
已经完成Rejected
失败态: 代表promise
已经失败- 当
promise
处于 Pending 状态时,可以转变 Fulfilled 或者 Rejected当
promise
处于Fulfilled
或Rejected
时,状态不能再发生改变
promise
中什么触发了状态的改变那?我们来看几个栗子:
// p1 什么都不执行且传入空函数
const p1 = new Promise(() => {});
console.log("p1: ", p1);
// p2 执行 resolve
const p2 = new Promise((resolve, reject) => {
resolve("success");
});
console.log("p2: ", p2);
// p3 执行 reject
const p3 = new Promise((resolve, reject) => {
reject("fail");
});
console.log("p3: ", p3);
// p4 抛出错误
const p4 = new Promise((resolve, reject) => {
throw Error("error");
});
console.log("p4: ", p4);
// p5 先执行 resolve 后执行 reject
const p5 = new Promise((resolve, reject) => {
resolve("success");
reject("fail");
});
console.log("p5: ", p5);
// p6 什么都不执行且不传参
const p6 = new Promise();
console.log("p6: ", p6);
我们来看一下输出结果:
从输出结果我们可以发现:
- 创建
promise
对象时,需传入一个执行函数(否则会报错,详见p6
),执行函数会立即执行 promise
的初始状态为pending
(见p1
)- 执行
resolve()
和reject()
可以将promise
的状态修改为fulfilled
和rejected
(见p2,p3
) - 若
promise
中抛出错误,相当于执行reject
(见p4
) promise
状态转变只能由pending
开始(见p5
)
学完这些,我们就可以来写第一版代码了。
实现基础 promise
—— 第一版
promise
构造函数实现
- 定义
promise
的三种状态
const PENDING = "PENDING";
const FULFILLED = "FULFILLED";
const REJECTED = "REJECTED";
- 定义
Promise
构造函数,添加必备属性
Promises/A+
规范中指出:
value
是任意的JavaScript
合法值(包括undefined
)reason
是用来表示promise
为什么被拒绝的原因
因此我们使用 ES6 class
定义 Promise
类,value/reason
分别赋值为 undefined
,状态 status
初始为 PENDING
class Promise {
constructor() {
this.value = undefined;
this.reason = undefined;
this.status = PENDING;
}
}
- 定义
promise
时需要传入执行器executor
executor
有两个参数,分别为resolve,reject
executor
会立即执行
class Promise {
constructor(executor) {
this.value = undefined;
this.reason = undefined;
this.status = PENDING;
const resolve = () => {};
const reject = () => {};
executor(resolve, reject);
}
}
- 实现
resolve
和reject
的功能resolve
函数可以将promise
由pending
转变为fulfilled
,并且更新promise
的value
值reject
函数可以将promise
由pending
转变为rejected
,并且更新promise
的reason
值注意:
promise
状态只能由Pending -> Fulfilled
和Pending -> Rejected
class Promise {
constructor(executor) {
this.value = undefined;
this.reason = undefined;
this.status = PENDING;
const resolve = (value) => {
// 判断当前的状态是否为Pending
// promise状态转变只能从 Pending 开始
if (this.status === PENDING) {
// 更新 value 值和 promise 状态
this.value = value;
this.status = FULFILLED;
}
};
const reject = (reason) => {
if (this.status === PENDING) {
this.reason = reason;
this.status = REJECTED;
}
};
executor(resolve, reject);
}
}
promise A+
规范规定,在有异常错误时,则执行失败函数。因此我们要捕获一下executor
的执行。
const PENDING = "PENDING";
const FULFILLED = "FULFILLED";
const REJECTED = "REJECTED";
class Promise {
constructor(executor) {
this.value = undefined;
this.reason = undefined;
this.status = PENDING;
const resolve = (value) => {
if (this.status === PENDING) {
this.value = value;
this.status = FULFILLED;
}
};
const reject = (reason) => {
if (this.status === PENDING) {
this.reason = reason;
this.status = REJECTED;
}
};
// 捕获 executor 异常
try {
executor(resolve, reject);
} catch (e) {
// 当发生异常时,调用 reject 函数
reject(e);
}
}
}
实现 then
方法的基本功能
then
方法的注意事项比较多,咱们一起阅读规范一起举例说明一下。
promise.then
接受两个参数:
promise.then(onFulfilled, onRejected);
onFulfilled
和onRejected
是可选参数,两者如果不是函数,则会**“忽略”**掉(真的是简单的忽略掉吗?请看下文分解)- 如果
onFulfilled
是一个函数,当promise
状态为Fulfilled
时,调用onFulfilled
函数,onRejected
类似,当promise
状态为Rejeted
时调用。
我们继续来看几个案例:
// 执行 resolve
const p1 = new Promise((resolve, reject) => {
resolve(1);
});
p1.then(
(v) => {
console.log("onFulfilled: ", v);
},
(r) => {
console.log("onRejected: ", r);
}
);
// 执行 reject
const p2 = new Promise((resolve, reject) => {
reject(2);
});
p2.then(
(v) => {
console.log("onFulfilled: ", v);
},
(r) => {
console.log("onRejected: ", r);
}
);
// 抛出异常
const p3 = new Promise((resolve, reject) => {
throw new Error("promise执行出现错误");
});
p3.then(
(v) => {
console.log("onFulfilled: ", v);
},
(r) => {
console.log("onRejected: ", r);
}
);
我们来看一下输出结果:
通过上面的输出结果,我们可以初步分析出 then
的调用逻辑
- 执行
resolve
后,promise
状态改变为fulfilled
,onFulfilled
函数调用,参数值为value
。 - 执行
reject
或 抛出错误,promise
状态改变为rejected
,onRejected
函数调用,参数值为reason
。
跟着上面的步骤,我们来模拟实现一下 then
const PENDING = "PENDING";
const FULFILLED = "FULFILLED";
const REJECTED = "REJECTED";
class Promise {
constructor(executor) {
this.value = undefined;
this.reason = undefined;
this.status = PENDING;
const resolve = (value) => {
if (this.status === PENDING) {
this.value = value;
this.status = FULFILLED;
}
};
const reject = (reason) => {
if (this.status === PENDING) {
this.reason = reason;
this.status = REJECTED;
}
};
// 捕获 executor 异常
try {
executor(resolve, reject);
} catch (e) {
reject(e);
}
}
then(onFulfilled, onRejected) {
// 当状态为 Fulfilled 时,调用 onFulfilled函数
if (this.status === FULFILLED) {
onFulfilled(this.value);
}
// 当状态为 Rejected 时,调用 onRejected 函数
if (this.status === REJECTED) {
onRejected(this.reason);
}
}
}
代码写到这里,第一版 promise
的代码就实现完成了。我们使用上面案例来测试一下,目前手写 promise
是否能实现基本功能。
输出结果如下:
跟原生 promise
的输出结果是相同的,因此,第一版 promise
实现是成功的。
promise
执行多次 then
方法
我们继续往下读规范:
如果一个 promise
调用多次 then
: 当 promise
状态为 Fulfilled
时,所有的 onFulfilled
函数按照注册顺序调用。当 promise
状态为 Rejected
时,所有的 onRejected
函数按照注册顺序调用。
这个规范讲的是什么意思那?小包来举个例子
const p = new Promise((resolve, reject) => {
resolve("success");
});
p.then((v) => {
console.log(v);
});
p.then((v) => {
console.log(`${v}--111`);
});
输出结果:
success;
success-- - 111;
通过上面的案例,将该条规范总结:同一个 promise
可以注册多个 then
方法,then
方法回调的执行按照注册顺序。
我们测试一下第一版手写 Promise
,输出结果为
success;
success--- 111;
第一版代码是可以满足当前规范的,~~~,放松一下,我们来继续实现。
处理异步逻辑——第二版
文章刚开始我们就讲过,promise
是异步编程的一种解决方案,那我们来测试一下第一版 Promise
是否可以实现异步。
const p = new Promise((resolve, reject) => {
// 使用 setTimeout 模拟一下异步
setTimeout(() => {
resolve("success");
});
});
p.then((v) => {
console.log(v);
});
p.then((v) => {
console.log(`${v}--111`);
});
没有任何输出,失败了,没事,我们来分析一下为何没有任何输出。
如果 Promise
内部存在异步调用,当执行到 then
函数时,此时由于 resolve/reject
处于异步回调之中,promise
的状态仍为 Pending
,因此 onFulfilled
和 onRejected
都无法执行。
那我们应该如何实现异步调用那?我们来分析一下:
- 如果
promise
还处于pending
状态时,就调用了then
方法,说明是存在异步的。 - 由于此时无法调用
onFulfilled/onRejected
,将onFulfilled/onRejected
存储起来 - 当异步回调中执行
resolve
或者reject
时,依次调用相应的onFulfilled/onRejected
代码实现
- 在
promise
中定义两个数组onFulfilledCallbacks
和onRejectedCallbacks
,分别用来存储onFulfilled
和onRejected
函数
class Promise {
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
}
then
方法执行时,若promise
处于Pending
状态,将onFulfilled
和onRejected
函数分别压入onFulfilledCallbacks
和onRejectedCallbacks
——形成订阅
then(onFulfilled, onRejected) {
if (this.status === FULFILLED) {
onFulfilled(this.value);
}
if (this.status === REJECTED) {
onRejected(this.reason);
}
if (this.status === PENDING) {
// 添加订阅
this.onFulfilledCallbacks.push(onFulfilled);
this.onRejectedCallbacks.push(onRejected);
}
}
- 当调用
resolve/reject
时,分别执行onFulfilledCallbacks
和onRejectedCallbacks
数组中的函数
const resolve = (value) => {
if (this.status === PENDING) {
this.value = value;
this.status = FULFILLED;
// 依次执行onFulfilled函数
this.onFulfilledCallbacks.forEach((cb) => cb(this.value));
}
};
const reject = (reason) => {
if (this.status === PENDING) {
this.reason = reason;
this.status = REJECTED;
// 依次执行onRejected函数
this.onRejectedCallbacks.forEach((cb) => cb(this.reason));
}
};
我们来整理一下代码,形成手写 Promise 第二版
const PENDING = "PENDING";
const FULFILLED = "FULFILLED";
const REJECTED = "REJECTED";
class Promise {
constructor(executor) {
this.value = undefined;
this.reason = undefined;
this.status = PENDING;
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
const resolve = (value) => {
if (this.status === PENDING) {
this.value = value;
this.status = FULFILLED;
this.onFulfilledCallbacks.forEach((cb) => cb(this.value));
}
};
const reject = (reason) => {
if (this.status === PENDING) {
this.reason = reason;
this.status = REJECTED;
this.onRejectedCallbacks.forEach((cb) => cb(this.reason));
}
};
try {
executor(resolve, reject);
} catch (e) {
console.log(e);
reject(e);
}
}
then(onFulfilled, onRejected) {
if (this.status === FULFILLED) {
onFulfilled(this.value);
}
if (this.status === REJECTED) {
onRejected(this.reason);
}
if (this.status === PENDING) {
// 添加订阅
this.onFulfilledCallbacks.push(onFulfilled);
this.onRejectedCallbacks.push(onRejected);
}
}
}
使用刚才的案例进行测试,输出结果为
success
success--111
第二版 Promise
已经可以成功支持异步逻辑了,撒花~~~ 休息一会,我们继续进行第三版的封装。
链式调用
我们继续去读规范:
then
方法必须返回一个promise
promise2 = promise1.then(onFulfilled, onRejected)
- 如果 onFulfilled 或 onRejected 返回值 x ,则运行
Promise Resolution Procedure [[Resolve]](promise2, x)
(这里暂且将他理解为 resolve(x))
console.log(new Promise((resolve) => {
resolve(1)
}).then((x) => x))
console.log(new Promise((resolve, reject) => {
reject(1)
}).then(undefined,(x) => x))
- 如果
onFulfilled
或onRejected
抛出异常e
,则promise2
调用reject(e)
console.log(new Promise((resolve) => {
resolve(1)
}).then(()=> {
throw new Error('resolve err')
}))
console.log(new Promise((resolve, reject) => {
reject(1)
}).then(undefined,()=> {
throw new Error('reject err')
}))
- 如果
onFulfilled
不是函数,且promise
状态为Fulfilled
,那么promise2
应该接受同样的值,同时状态为Fulfilled
// 输出结果 1
const p1 = new Promise((resolve) => {
resolve(1)
})
p1.then(x => x).then().then().then().then().then(x=> console.log(x))
- 如果
onRejected
不是函数,且promise
状态为Rejected
,那么promise2
应该接受同样的原因,同时状态为Rejected
// 输出结果 Error: error at <anonymous>:4:33
const p1 = new Promise((resolve) => {
reject(1)
})
p1.then(undefined, () => {throw Error('error')}).then().then().then().then().then(x=> console.log(x), (r)=> console.log(r))
代码实现
那我们就照着规范一步一步的实现它
then
需要返回一个promise
,递归调用Promise
,在then
中定义一个promise
并返回
then(onFulfilled, onRejected) {
if (this.status === FULFILLED) {
onFulfilled(this.value);
}
if (this.status === REJECTED) {
onRejected(this.reason);
}
if (this.status === PENDING) {
this.onFulfilledCallbacks.push(onFulfilled);
this.onRejectedCallbacks.push(onRejected);
}
// 返回 promise2
const promise2 = new Promise((resolve, reject)=> {})
return promise2;
}
- 如果
onFulfilled
或onRejected
返回值x
,则调用返回promise2
的resolve
函数。
由于需要调用 promise2 deresolve
函数,那说明 onFulfilled
和 onRejected
调用发生在 promise2
的 executor
中。
难点在于如何处理 Pending
状态,当 promise
处于 Pending
状态时,我们将 onFulfilled
和 onRejected
函数先存储起来。但是链式调用需要 onFulfilled/onRejected
函数的返回值,因此我们不能简单的将函数存储起来。
我们将函数封装成下面效果,然后存储起来。
() => {
let x = onFulfilled(this.value);
resolve(x);
}
因此目前的 then
方法:
then(onFulfilled, onRejected) {
let promise2 = new Promise((resolve, reject) => {
if (this.status === FULFILLED) {
let x = onFulfilled(this.value);
resolve(x);
}
if (this.status === REJECTED) {
let x = onRejected(this.reason);
resolve(x);
}
if (this.status === PENDING) {
this.onFulfilledCallbacks.push(() => {
let x = onFulfilled(this.value);
resolve(x);
});
this.onRejectedCallbacks.push(() => {
let x = onRejected(this.reason);
resolve(x);
});
}
});
return promise2;
}
- 如果
onFulfilled
或onRejected
抛出异常 e ,则promise2
调用reject(e)
使用 try catch
捕捉函数执行,当出现异常,执行 reject(e)
then(onFulfilled, onRejected) {
let p1 = new Promise((resolve, reject) => {
if (this.status === FULFILLED) {
try {
let x = onFulfilled(this.value);
resolve(x);
} catch (e) {
reject(e);
}
}
if (this.status === REJECTED) {
try {
let x = onRejected(this.reason);
resolve(x);
} catch (e) {
reject(e);
}
}
if (this.status === PENDING) {
// 添加订阅
this.onFulfilledCallbacks.push(() => {
try {
let x = onFulfilled(this.value);
resolve(x);
} catch (e) {
reject(e);
}
});
this.onRejectedCallbacks.push(() => {
try {
let x = onRejected(this.reason);
resolve(x);
} catch (e) {
reject(e);
}
});
}
});
return p1;
}
- 处理
onFulfilled
和onRejected
非函数情况。
通过上面的案例,我们可以发现,当 onFulfilled
不是函数时,then
方法将其的值传递下去;当 onRejected
不是函数时,then
方法同样会传递错误。
我们如何才能连续传递值或者异常那?我们来看一下下面这两个函数,是否能满足要求
x => x;
e => throw e;
const PENDING = "PENDING";
const FULFILLED = "FULFILLED";
const REJECTED = "REJECTED";
class Promise {
constructor(executor) {
this.value = undefined;
this.reason = undefined;
this.status = PENDING;
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
// this指向问题
const resolve = (value) => {
if (this.status === PENDING) {
this.value = value;
this.status = FULFILLED;
this.onFulfilledCallbacks.forEach((cb) => cb(this.value));
}
};
const reject = (reason) => {
if (this.status === PENDING) {
this.reason = reason;
this.status = REJECTED;
this.onRejectedCallbacks.forEach((cb) => cb(this.reason));
}
};
try {
executor(resolve, reject);
} catch (e) {
reject(e);
}
}
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (v) => v;
onRejected =
typeof onRejected === "function"
? onRejected
: (e) => {
throw e;
};
let promise2 = new Promise((resolve, reject) => {
if (this.status === FULFILLED) {
try {
let x = onFulfilled(this.value);
resolve(x);
} catch (e) {
reject(e);
}
}
if (this.status === REJECTED) {
try {
let x = onRejected(this.reason);
resolve(x);
} catch (e) {
reject(e);
}
}
if (this.status === PENDING) {
// 添加订阅
this.onFulfilledCallbacks.push(() => {
try {
let x = onFulfilled(this.value);
resolve(x);
} catch (e) {
reject(e);
}
});
this.onRejectedCallbacks.push(() => {
try {
let x = onRejected(this.reason);
resolve(x);
} catch (e) {
reject(e);
}
});
}
});
return promise2;
}
}
promise
原生编写还差另一个核心部分 resolvePromise
,resolvePromise
内部包含很多规范,因此小包准备另开一篇文章详解。