【前端进阶】用 Typescript 手写 Promise,A+ 规范,可用 async、await 语法糖

691 阅读3分钟

本文相关技术栈: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; 去判断是否已经修改状态。

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);
//   };