【前端進階】用 Typescript 手寫 Promise,A+ 規範,可用 async、await 語法糖

語言: CN / TW / HK

theme: condensed-night-purple highlight: vs2015


本文相關技術棧:js、ts、react、es6。(只用 js 也行)

A+規範原文件

Promises/A+ (promisesaplus.com)

使用效果與原生promise相同

image.png

這裡的 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 接受兩個回撥,可以為空。

  1. 返回一個新的 promise 去做鏈式呼叫

  2. 判斷 promise 狀態

  3. 賦值回撥事件

這裡用到了 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); // }; ```