跳到主要内容

用 Typescript 手写 Promise,A+ 规范,可用 async、await 语法糖

本文相关技术栈:js、ts、react、es6。(只用 js 也行)

A+规范原文档

Promises/A+ (promisesaplus.com)

使用效果与原生promise相同

这里的 then 回调嵌套只是展示 promise 链式调用,代码中请勿使用 then 嵌套回调。

Promise 代码分析

链式: new promise => then / catch / finally

静态方法: static promise => race / all / resolve / reject

实现

首先,要了解一些相关机制如 微/宏任务,Promise,async/await 等。

这里就不展开描述,可以看另一篇文章:【前端进阶】详解 微任务、宏任务、异步阻塞、事件循环、单例模式 - 掘金 (juejin.cn)

其次,要想使用 async await 语法糖就需要符合相关的代码规范,因为这段代码是短时间整出来的,那么短时间要如何去看具体规范细节呢?

就要用到 TypeScript

比如 要查看原生 Promise 的方法细节以及返回参数之类的,可以把鼠标 hover 到原生的 new Promise().resolve() 上,就可以展示相关方法的参数,类型,注释、介绍等。

不用一点点翻文档,方便后续开发效率的提升。

思路

promise 的状态。 那么地球人都知道,promise有三个状态,并且不能重复改变,所以这里定义一个枚举类去管理 promise 的状态。

后续通过 if (this.status !== PromiseStatus.pending) return; 去判断是否已经修改状态。

enum PromiseStatus {
pending,
resolved,
rejected,
}

resolve & resject 的实现

通过判断去执行相对应的 onXX 回调事件,如 then 的两个回调,以及catch、finally。

private resolve = (value: any): any => {
if (this.status !== PromiseStatus.pending) return;
this.status = PromiseStatus.resolved;
this.thenable = value;
this.onFulfilled?.(value);
// console.log('进入resolve', value);
};
private reject = (reason: any): any => {
if (this.status !== PromiseStatus.pending) return;
this.status = PromiseStatus.rejected;
this.thenable = reason;
this.onRejected?.(reason);
// console.log('进入reject', reason);
};

then

  1. then 接受两个回调,可以为空。
  2. 返回一个新的 promise 去做链式调用
  3. 判断 promise 状态
  4. 赋值回调事件

这里用到了 queueMicrotask,用于把 then 回调任务放入任务队列中。

Q: 为什么不用 setTimeout?

A: setTimeout 优先级比 promise 低,如果使用 setTimeout,执行顺序在原生 Promise 后。

then = (onFulfilled?: (val: any) => any, onRejected?: (val: any) => any) => {
return new CustomPromise((resolve, reject) => {
// console.log('进入then', this.status);
if (this.status === PromiseStatus.pending) {
this.onFulfilled = (value) =>
queueMicrotask(() => {
const resolveValue = onFulfilled?.(value);
resolve(resolveValue);
}); // microtask
this.onRejected = (reason) =>
queueMicrotask(() => {
const resolveValue = onRejected?.(reason);
resolve(resolveValue);
}); // microtask
} else {
// console.log('进入链式调用', this.thenable);
resolve(this.thenable); // then 返回的 promise 在下一个then中是 resolve 的
}
// microtask
queueMicrotask(() => {
this.status === PromiseStatus.resolved && onFulfilled?.(this.thenable);
this.status === PromiseStatus.rejected && onRejected?.(this.thenable);
});
});
};

源码

race、all 这两个方法后续会更新上来🕊️🕊️🕊️。

代码中的 any 类型是因为 resolve / reject 传参和返回的值是任意的。

TS 开发者,要想清楚再用 any 类型。

使用方式

与 原生 Promise 一致,通过 promise 设计规范即可使用 async / await 语法糖。

import "./App.css";
import CustomPromise from "./utils/customPromise";

function App() {
const handleClick = async () => {
const res = await new CustomPromise((resolve) => {
setTimeout(() => {
resolve("async success");
}, 1000);
}).then(async (val) => {
console.log(`then val:${val}`);
await CustomPromise.resolve().then(() => {
console.log("then await resolve");
});
await CustomPromise.reject()
.then(() => {
console.log("then await reject");
throw new Error("抛出错误:404");
})
.then((err) => console.log(`捕捉报错:${err}`));
return "我是回调";
});
console.log(res);
};
return (
<div className="App">
<header className="App-header">
<button onClick={handleClick}>click button</button>
</header>
</div>
);
}

export default App;

源码类

大概 70 行代码,可以复制到本地试试。

tsc ./demo.ts
node ./demo.js
// source code
// from weipengzou
enum PromiseStatus {
pending,
resolved,
rejected,
}
class CustomPromise {
// status
private status: PromiseStatus = PromiseStatus.pending;
// thenable
private thenable?: any;

private onFulfilled?: (value: any) => any;
private onRejected?: (reason: any) => any;

// constructor
constructor(executor: (resolve: (value: any) => void, reject: (value: any) => void) => void) {
try {
executor(this.resolve, this.reject);
} catch (error) {
this.reject(error);
}
}
static resolve = () => new CustomPromise((resolve, reject) => resolve(""));
static reject = () => new CustomPromise((resolve, reject) => reject(""));
private resolve = (value: any): any => {
if (this.status !== PromiseStatus.pending) return;
this.status = PromiseStatus.resolved;
this.thenable = value;
this.onFulfilled?.(value);
// console.log('进入resolve', value);
};
private reject = (reason: any): any => {
if (this.status !== PromiseStatus.pending) return;
this.status = PromiseStatus.rejected;
this.thenable = reason;
this.onRejected?.(reason);
// console.log('进入reject', reason);
};
then = (onFulfilled?: (val: any) => any, onRejected?: (val: any) => any) => {
return new CustomPromise((resolve, reject) => {
// console.log('进入then', this.status);
if (this.status === PromiseStatus.pending) {
this.onFulfilled = (value) =>
queueMicrotask(() => {
const resolveValue = onFulfilled?.(value);
resolve(resolveValue);
}); // microtask
this.onRejected = (reason) =>
queueMicrotask(() => {
const resolveValue = onRejected?.(reason);
resolve(resolveValue);
}); // microtask
} else {
// console.log('进入链式调用', this.thenable);
resolve(this.thenable); // then 返回的 promise 在下一个then中是 resolve 的
}
// microtask
queueMicrotask(() => {
this.status === PromiseStatus.resolved && onFulfilled?.(this.thenable);
this.status === PromiseStatus.rejected && onRejected?.(this.thenable);
});
});
};
}
export default CustomPromise;

// example
// const handleClick = async () => {
// const res = await new CustomPromise((resolve) => {
// setTimeout(() => {
// resolve('async success');
// }, 1000);
// }).then(async (val) => {
// console.log(`then val:${val}`);
// await CustomPromise.resolve().then(() => {
// console.log('then await resolve');
// });
// await CustomPromise.reject()
// .then(() => {
// console.log('then await reject');
// throw new Error('抛出错误:404');
// })
// .then((err) => console.log(`捕捉报错:${err}`));
// return '我是回调';
// });
// console.log(res);
// };