这篇文章我们用 js 来实现一个简易版的 promise。promise 有很多的 api 语法,很多细节,让人学了很难记住。而学习源码,能让我们拨开迷雾,直接抵达其本质。
Promise 状态和值
promise 有三个状态:
- pending
- fulfilled
- rejected
一般情况下,刚开始创建的 promise 的状态是 pending ,也就是待定(不确定)的意思。状态确定之后,会变成 fulfilled 或 rejected 其中一种,一旦状态确定了,就不能改变了。
并且 promise 也有自己的值,其把状态改变函数的参数作为 promise 的值。
下面实现下。新建文件夹 MyPromise,然后创建 index.js :
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";
class MyPromise {
status = "pending";
value = undefined;
constructor(executor) {
executor(this.resolve.bind(this), this.reject.bind(this));
return this;
}
resolve(value) {
if (this.status === PENDING) {
this.status = FULFILLED;
this.value = value;
}
}
reject(reason) {
if (this.status === PENDING) {
this.status = REJECTED;
this.value = reason;
}
}
}
module.exports = MyPromise;
上面的代码实现了一个基本的 Promise 类,包含状态管理和 resolve、reject 方法。
jest测试:
测试我们就不用简单的手动测试,借用 jest 测试框架,体验下自动化测试的感觉😄
**Jest **是一个由 Facebook 开发和维护的 JavaScript 测试框架,广泛用于测试 React 应用程序,但也可以用于测试任何 JavaScript 代码。
要使用 Jest,首先需要安装它:
npm init -y
npm install --save-dev jest
npm init -y之后,MyPromise 文件夹下面会多一个 package.json。在 package.json 中添加一个测试脚本:
"scripts": {
"test": "jest"
}
创建个测试文件看看 jest 语法长什么样子。demo.test.js (通常以 .test.js 或 .spec.js 结尾):
test('adds 1 + 2 to equal 3', () => {
expect(1 + 2).toBe(3);
});
然后打开终端,执行npm run test:
测试通过!!
下面我们正式写 promise 的测试代码: promise.test.js:
const MyPromise = require(".");
describe("test promise status and value", () => {
test("test resolve", () => {
const myPromise = new MyPromise((resolve, reject) => {
resolve(1);
});
expect(myPromise.status).toBe("fulfilled");
expect(myPromise.value).toBe(1);
});
test("test reject", () => {
const myPromise = new MyPromise((resolve, reject) => {
reject(1);
});
expect(myPromise.status).toBe("rejected");
expect(myPromise.value).toBe(1);
});
test("test resolve and reject at the same time", () => {
const myPromise = new MyPromise((resolve, reject) => {
resolve(1);
reject(2);
});
expect(myPromise.status).toBe("fulfilled");
expect(myPromise.value).toBe(1);
});
});
代码中写了三个测试用例,用来测试 promise 的 status 以及 value。
测试结果:
jest 测试看起来有点懵?看不懂?没关系,看个大概就好,把重点放在 promise 的实现上
promise 的 then
then 的性质可就多了,下面一个一个来
then 的参数
then 用来读取 promise的值,并接收两个函数作为参数。如果 promise 的状态是 fulfilled,第一个参数就会被调用,并且传入 promise 的值。如果 promise的状态是 rejected,第二个参数就会被调用
class MyPromise {
status = "pending";
value = undefined;
constructor(executor) {
executor(this.resolve.bind(this), this.reject.bind(this));
return this;
}
resolve(value) {
if (this.status === PENDING) {
this.status = FULFILLED;
this.value = value;
}
}
reject(reason) {
if (this.status === PENDING) {
this.status = REJECTED;
this.value = reason;
}
}
// 添加一个then
then(onFulfilled, onRejected) {
if (this.status === FULFILLED) {
onFulfilled(this.value);
} else if (this.status === REJECTED) {
onRejected(this.value);
}
}
}
thne 的返回值
then 的返回值是一个新 promise,并且新 promise 的状态和值与onFulfilled、onRejected的返回值有关系:
onFulfilled/onRejected可以返回任意值,如果返回的是非 promise 值,那么生成的新 promise 的状态都是fulfilled,并且它的值和 onFulfilled 的返回值一致- 如果
onFulfilled/onRejected返回的是promise对象,那么生成的新promise的状态和返回的promise的状态一致,且值也是一致的
class MyPromise {
//...
then(onFulfilled, onRejected) {
return new MyPromise((resolve, reject) => {
if (this.status === FULFILLED) {
this.resolvePromise(promise, onFulfilled(this.value), resolve, reject);
} else if (this.status === REJECTED) {
this.resolvePromise(promise, onRejected(this.value), resolve, reject);
}
});
}
handlePromise(x, resolve, reject) {
if (x instanceof MyPromise) {
x.then(
(res) => {
return this.handlePromise(res, resolve, reject);
},
(err) => {
reject(err);
}
);
} else {
resolve(x);
}
}
}
写个测试脚本测试下,先测试 then 的参数:
describe("test promise then", () => {
test("test then on fulfilled promise", () => {
const myPromise = new MyPromise((resolve, reject) => {
resolve(1);
});
const spyOnFulfilled = jest.fn();
const spyOnRejected = jest.fn();
myPromise.then(spyOnFulfilled, spyOnRejected);
expect(spyOnFulfilled).toHaveBeenCalledWith(1);
expect(spyOnRejected).not.toHaveBeenCalled();
});
test("test then on rejected promise", () => {
const myPromise = new MyPromise((resolve, reject) => {
reject(1);
});
const spyOnFulfilled = jest.fn();
const spyOnRejected = jest.fn();
myPromise.then(spyOnFulfilled, spyOnRejected);
expect(spyOnFulfilled).not.toHaveBeenCalled();
expect(spyOnRejected).toHaveBeenCalledWith(1);
});
}
jest.fn是 jest 中 mock 的函数,用来检测函数是否被调用
测试结果:
再测试下 then 的返回值:
describe("test return value of then", () => {
test("test return value of then", () => {
const myPromise = new MyPromise((resolve, reject) => {
resolve(1);
});
const spyOnFulfilled = jest.fn((res) => {
return res + 1;
});
const spyOnRejected = jest.fn();
const newPromise = myPromise.then(spyOnFulfilled, spyOnRejected);
expect(newPromise.status).toBe("fulfilled");
expect(newPromise.value).toBe(2);
});
test("test return fulfilled promise of then", () => {
const myPromise = new MyPromise((resolve, reject) => {
resolve(1);
});
const spyOnFulfilled = jest.fn((res) => {
return new MyPromise((resolve, reject) => {
resolve(res + 1);
});
});
const spyOnRejected = jest.fn();
const newPromise = myPromise.then(spyOnFulfilled, spyOnRejected);
expect(newPromise.status).toBe("fulfilled");
expect(newPromise.value).toBe(2);
});
test("test return rejected promise of then", () => {
const myPromise = new MyPromise((resolve, reject) => {
resolve(1);
});
const spyOnFulfilled = jest.fn((res) => {
return new MyPromise((resolve, reject) => {
reject(res + 1);
});
});
const spyOnRejected = jest.fn();
const newPromise = myPromise.then(spyOnFulfilled, spyOnRejected);
expect(newPromise.status).toBe("rejected");
expect(newPromise.value).toBe(2);
});
});
测试结果:
在 then 中抛出一个错误
如果想使 then 中返回一个失败的 promise,还有一个办法,就是在 then 中 抛出一个错误。要想捕获 then 中抛出错误,还需要在 then 中处理一下:
class MyPromise{
then(onFulfilled, onRejected) {
return new MyPromise((resolve, reject) => {
try {
} catch (error) {}
if (this.status === FULFILLED) {
try {
const res = onFulfilled(this.value);
this.handlePromise(res, resolve, reject);
} catch (error) {
reject(error);
}
} else if (this.status === REJECTED) {
try {
const res = onRejected(this.value);
this.handlePromise(res, resolve, reject);
} catch (error) {
reject(error);
}
}
});
}
}
在调用onFulfilled/onRejected的时候,外部包裹了 try...catch,如果抛出错误,可以直接返回一个失败的 promise
测试代码:
describe("test throw error in then", () => {
test("test throw error in then", () => {
const myPromise = new MyPromise((resolve, reject) => {
resolve(1);
});
let newPromise = myPromise.then(
(res) => {
throw "error";
},
(err) => {
console.log(err);
}
);
expect(newPromise.status).toBe("rejected");
expect(newPromise.value).toBe("error");
});
});
测试结果:
then 的参数省略
then 的参数其实没必要一定要穿两个,可以只传一 onFulfilled,或者什么都不传。支持这个特性,是因为 then 中给了默认值
then(onFulfilled, onRejected) {
// 给默认值
onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (value) => value;
onRejected =
typeof onRejected === "function"
? onRejected
: (reason) => {
throw reason;
};
return new MyPromise((resolve, reject) => {
try {
} catch (error) {}
if (this.status === FULFILLED) {
try {
const res = onFulfilled(this.value);
this.handlePromise(res, resolve, reject);
} catch (error) {
reject(error);
}
} else if (this.status === REJECTED) {
try {
const res = onRejected(this.value);
this.handlePromise(res, resolve, reject);
} catch (error) {
reject(error);
}
}
});
}
then 的等待性
很多时候,我们并不是在状态确定的 promise 上调用 then,而是在 pending 的 promise 上调用的,向这样:
new MyPromise((resolve, reject)=>{
setTimeout(resolve,1000,1);
}).then(res=>{
console.log('onfulfilled',res)
})
上面的代码创建了一个 1 秒之后,状态才落定的 promise 对象,而 then 是同步读取的。目前 then 中并没有对 pending 状态的判断处理,所以上面 then 中的函数并不会被执行。
按道理,then 中的函数是需要被执行,而且是 1 秒之后才执行,这样才能拿到 promise 的值。代码怎么写呢?
class MyPromise {
onFulfilledCallbacks = [];
onRejectedCallbacks = [];
resolve(value) {
if (this.status === PENDING) {
this.status = FULFILLED;
this.value = value;
this.onFulfilledCallbacks.forEach((callback) => {
callback(value);
});
}
}
reject(reason) {
if (this.status === PENDING) {
this.status = REJECTED;
this.value = reason;
this.onRejectedCallbacks.forEach((callback) => {
callback(reason);
});
}
}
return new MyPromise((resolve, reject) => {
// 状态成功时候的处理函数
const handleResolve = () => {
try {
const res = onFulfilled(this.value);
this.handlePromise(res, resolve, reject);
} catch (error) {
reject(error);
}
};
// 状态失败时候的处理函数
const handleReject = () => {
try {
const res = onRejected(this.value);
this.handlePromise(res, resolve, reject);
} catch (error) {
reject(error);
}
};
if (this.status === PENDING) {
this.onFulfilledCallbacks.push(() => {
handleResolve();
});
this.onRejectedCallbacks.push(() => {
handleReject();
});
} else if (this.status === FULFILLED) {
handleResolve();
} else if (this.status === REJECTED) {
handleReject();
}
});
}
代码用了一个巧妙的方式,当 promise 的状态是 pending 的时候,会将 then 函数传入的onFulfilled/onRejected给保存起来,等到状态确定的时候,才调用对应的函数。
完美解决😄
测试代码:
test("test pending", (done) => {
jest.useFakeTimers();
const onFulfilled = jest.fn();
const onRejected = jest.fn();
const myPromise = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve(1);
}, 1000);
}).then(onFulfilled, onRejected);
expect(myPromise.status).toBe("pending");
setTimeout(() => {
expect(onFulfilled).toHaveBeenCalledWith(1);
expect(onRejected).not.toHaveBeenCalled();
done();
}, 1000);
jest.advanceTimersByTime(1000);
jest.useRealTimers();
});
测试结果:
这里的测试代码有一个小技巧,为了不让测试结果出来真的被阻塞 1 秒,我使用了 jest 的 fakeTime,让测试用例在 2ms 就能判断出结果
then 的异步性
真实的 then 是异步执行的,我们现在代码是同步的,现在改一改:
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (value) => value;
onRejected =
typeof onRejected === "function"
? onRejected
: (reason) => {
throw reason;
};
return new MyPromise((resolve, reject) => {
const handleResolve = () => {
queueMicrotask(() => {
try {
const res = onFulfilled(this.value);
this.handlePromise(res, resolve, reject);
} catch (error) {
reject(error);
}
});
};
const handleReject = () => {
queueMicrotask(() => {
try {
const res = onRejected(this.value);
this.handlePromise(res, resolve, reject);
} catch (error) {
reject(error);
}
});
};
if (this.status === PENDING) {
this.onFulfilledCallbacks.push(() => {
handleResolve();
});
this.onRejectedCallbacks.push(() => {
handleReject();
});
} else if (this.status === FULFILLED) {
handleResolve();
} else if (this.status === REJECTED) {
handleReject();
}
});
}
我使用了queueMicrotask来实现异步的微任务效果
改完之后,测试代码出现了问题:
因为之前的 Promise 的测试写法是同步的写法,所以没等到结果出来,断言就开始了,所以会错:
test("test then on fulfilled promise", () => {
const myPromise = new MyPromise((resolve, reject) => {
resolve(1);
});
const spyOnFulfilled = jest.fn();
const spyOnRejected = jest.fn();
myPromise.then(spyOnFulfilled, spyOnRejected);
// 同步断言!
expect(spyOnFulfilled).toHaveBeenCalledWith(1);
expect(spyOnRejected).not.toHaveBeenCalled();
});
所有判断 then 的返回值的代码,都需要改改:
describe("test promise then", () => {
test("test then on fulfilled promise", () => {
const myPromise = new MyPromise((resolve, reject) => {
resolve(1);
});
const spyOnFulfilled = jest.fn();
const spyOnRejected = jest.fn();
// 异步断言
myPromise.then(spyOnFulfilled, spyOnRejected).then(() => {
expect(spyOnFulfilled).toHaveBeenCalledWith(1);
expect(spyOnRejected).not.toHaveBeenCalled();
});
});
});
describe("test return value of then", () => {
test("test return value of then", () => {
const myPromise = new MyPromise((resolve, reject) => {
resolve(1);
});
const spyOnFulfilled = jest.fn((res) => {
return res + 1;
});
const spyOnRejected = jest.fn();
const newPromise = myPromise.then(spyOnFulfilled, spyOnRejected);
// 异步断言
newPromise.then(() => {
expect(newPromise.status).toBe("fulfilled");
expect(newPromise.value).toBe(2);
});
});
});
上面只给了部分代码,就不全写出来了
其实,修改测试代码本身,不就说明 then 的异步性生效了吗,其异步性还要单独写测试代码吗?
还是写一下吧😄。我打算通过函数执行的顺序来证明其异步性:
describe("test async of then", (done) => {
test("test async of then", async () => {
const myPromise = new MyPromise((resolve, reject) => {
resolve(1);
});
const fn1 = jest.fn();
const fn2 = jest.fn();
const fn3 = jest.fn();
fn1();
const newPromise = myPromise.then(
() =>fn2(),
() => {}
);
fn3();
newPromise.then(() => {
const order1 = fn1.mock.invocationCallOrder[0];
const order2 = fn2.mock.invocationCallOrder[0];
const order3 = fn3.mock.invocationCallOrder[0];
expect(order1).toBeLessThan(order2);
expect(order3).toBeLessThan(order2);
done();
});
});
});
测试结果:
promise 的 catch
**<font style="color:rgb(62, 175, 124);">catch</font>** 有点像 **<font style="color:rgb(62, 175, 124);">try...catch</font>**的写法,**<font style="color:rgb(62, 175, 124);">catch</font>** 中的代码,在 **<font style="color:rgb(62, 175, 124);">try</font>** 中抛出错误之后,就会被执行。而 **<font style="color:rgb(62, 175, 124);">promise</font>**中的 **<font style="color:rgb(62, 175, 124);">catch</font>**回调函数 ,在链式调用过程中抛出了错误,就会被执行。
class MyPromise{
catch(onRejected){
return this.then(null, onRejected);
}
}
其实 catch 就是 then 函数的语法糖
测试代码:
test("test catch in fulfilled promise", async () => {
const myPromise = new MyPromise((resolve, reject) => {
resolve(1);
});
const spyOnCatch = jest.fn(() => {
// 该断言不会被执行
expect(res).toBe(1);
});
const res = await myPromise.catch(() => {});
expect(spyOnCatch).not.toHaveBeenCalled();
expect(res).toBe(1);
// 判断断言执行的数量为2
expect.assertions(2);
});
test("test catch in rejected promise", async () => {
const myPromise = new MyPromise((resolve, reject) => {
reject(1);
});
const spyOnCatch = jest.fn((res) => {
// 该断言会被执行
expect(res).toBe(1);
return "ERROR";
});
const res = await myPromise.catch(spyOnCatch);
expect(spyOnCatch).toHaveBeenCalled();
expect(res).toBe("ERROR");
// 判断断言执行的数量为3
expect.assertions(3);
});
测试结果:
promise 的 finally
finally 中回调函数,无论 promise 是失败还是成功,都会执行,不过 finally 中拿不到 promise 的值。 finally 后面还可以继续 then(因为 finally 也会返回 promise), 并且会原封不动的将状态和值传递给后面的 then
透露下,finally 也是 then 的语法糖,思考下,如何用 then 函数实现这一特性
class MyPromise {
finally(onFinally) {
onFinally = (res) => {
onFinally();
return res;
};
return this.then(onFinally, onFinally);
}
}
测试代码:
test("test finally in fulfilled promise", async () => {
const myPromise = new MyPromise((resolve, reject) => {
resolve(1);
});
const spyOnFinally = jest.fn();
const res = await myPromise.finally(spyOnFinally);
expect(spyOnFinally).toHaveBeenCalled();
expect(res).toBe(1);
});
test("test finally in rejected promise", async () => {
const myPromise = new MyPromise((resolve, reject) => {
reject(1);
});
const spyOnFinally = jest.fn();
const res = await myPromise.then().finally(spyOnFinally);
expect(spyOnFinally).toHaveBeenCalled();
expect(res).toBe(1);
});
测试结果:
完整代码
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";
class MyPromise {
status = "pending";
value = undefined;
onFulfilledCallbacks = [];
onRejectedCallbacks = [];
constructor(executor) {
executor(this.resolve.bind(this), this.reject.bind(this));
return this;
}
resolve(value) {
if (this.status === PENDING) {
this.status = FULFILLED;
this.value = value;
this.onFulfilledCallbacks.forEach((callback) => {
callback(value);
});
}
}
reject(reason) {
if (this.status === PENDING) {
this.status = REJECTED;
this.value = reason;
this.onRejectedCallbacks.forEach((callback) => {
callback(reason);
});
}
}
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (value) => value;
onRejected =
typeof onRejected === "function"
? onRejected
: (reason) => {
throw reason;
};
return new MyPromise((resolve, reject) => {
const handleResolve = () => {
queueMicrotask(() => {
try {
const res = onFulfilled(this.value);
this.handlePromise(res, resolve, reject);
} catch (error) {
reject(error);
}
});
};
const handleReject = () => {
queueMicrotask(() => {
try {
const res = onRejected(this.value);
this.handlePromise(res, resolve, reject);
} catch (error) {
reject(error);
}
});
};
if (this.status === PENDING) {
this.onFulfilledCallbacks.push(() => {
handleResolve();
});
this.onRejectedCallbacks.push(() => {
handleReject();
});
} else if (this.status === FULFILLED) {
handleResolve();
} else if (this.status === REJECTED) {
handleReject();
}
});
}
handlePromise(x, resolve, reject) {
if (x instanceof MyPromise) {
x.then(
(res) => {
return this.handlePromise(res, resolve, reject);
},
(err) => {
reject(err);
}
);
} else {
resolve(x);
}
}
catch(onRejected) {
return this.then(null, onRejected);
}
finally(onFinally) {
onFinally = (res) => {
onFinally();
return res;
};
return this.then(onFinally, onFinally);
}
}
module.exports = MyPromise;
代码地址: github.com/zenoskongfu…
所有的测试均已通过:
这个版本实现的功能是基础的,方便初学者学习的,所以省略了很多边界条件,与真实Promise 实用性上有些差距。
但是其核心功能已经全部实现,且核心实现也是贴近真实 Promise 的,大家可以放心食用
总结
到这里 then 以及 promise 相关的实例方法都介绍地差不多了。
下篇文章来实现 promise 的静态方法:
Promise.resolve(value)—— 使用给定 value ,直接创建一个 resolved 的 promise。Promise.reject(error)—— 使用给定 error,直接创建一个 rejected 的 promise。Promise.all(promises)—— 等待所有 promise 都 resolve 时,返回存放它们结果的数组。如果给定的任意一个 promise 为 reject,那么它就会变成 Promise.all 的 error,所有其他 promise 的结果都会被忽略。Promise.race(promises)—— 等待第一个 settle 的 promise,并将其 result/error 作为结果返回。Promise.any(promises)(ES2021 新增方法)—— 等待第一个 fulfilled 的 promise,并将其结果作为结果返回。如果所有 promise 都 rejected,Promise.any 则会抛出 AggregateError 错误类型的 error 实例。Promise.allSettled(promises)(ES2020 新增方法)—— 等待所有 promise 都 settle 时,并以包含以下内容的对象数组的形式返回它们的结果:- status: "fulfilled" 或 "rejected"
- value(如果 fulfilled)或 reason(如果 rejected)。