【前端進階】用 Typescript 手寫 Promise,A+ 規範,可用 async、await 語法糖
theme: condensed-night-purple highlight: vs2015
本文相關技術棧:js、ts、react、es6。(只用 js 也行)
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;
去判斷是否已經修改狀態。
ts
enum PromiseStatus {
pending,
resolved,
rejected,
}
resolve & resject 的實現
通過判斷去執行相對應的 onXX 回撥事件,如 then 的兩個回撥,以及catch、finally。
ts
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 接受兩個回撥,可以為空。
-
返回一個新的 promise 去做鏈式呼叫
-
判斷 promise 狀態
-
賦值回撥事件
這裡用到了 queueMicrotask,用於把 then 回撥任務放入任務佇列中。
Q: 為什麼不用 setTimeout?
A: setTimeout 優先順序比 promise 低,如果使用 setTimeout,執行順序在原生 Promise 後。
ts
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 語法糖。 ```tsx 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 (
export default App;
```
原始碼類
大概 70 行程式碼,可以複製到本地試試。
cmd
tsc ./demo.ts
cmd
node ./demo.js
```ts // 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);
// };
```