手写promise等

一、前提了解

关于promise:

首先,我们都知道它有三个状态,可以进行异步操作,并且有错误的处理机制。除此之外,它是可以链式调用的。常用的几个方法:then、catch、finally,以及all、race、resolve、reject等方法。

接着简单地了解一下它产生的背景。

众所周知,JavaScript是单线程的,因此必须支持异步编程才能提高运行效率。异步编程的语法目标是让异步过程写起来像同步过程。

回调函数是最简单的一种方式,但是其容易形成回调地狱,即多个回调函数嵌套,降低代码可读性,容易出错。

于是ES6中出现了Promise,它有三种状态,pending、fulfilled、rejected,并且可以链式调用。Promise实际上是利用编程技巧将回调函数的横向加载,改成纵向加载,达到链式调用的效果,避免回调地狱。最大问题是代码冗余,原来的任务被Promise包装了一下,会有很多then,catch。当然这样也会导致代码的复杂性,所以后面又出现了async、await(ES8中被提出来)。

这是其产生的背景和作用,那么接着说我们知道的,promise的构成:

1、三个状态:pending、fulfilled、rejected

2、构造函数:执行器函数executor

3、then、catch、finally、resolve、reject、race、all、allSettle等方法,一般了解前三个就好


二、开始实现

先实现简单的,三个状态的变化,以及基本的then方法。
已知三个状态:

  1. Pending(待定):初始状态,操作尚未完成。
  2. Fulfilled(已兑现):操作成功完成,并返回一个值。
  3. Rejected(已拒绝):操作失败,并返回一个原因。

Promise 的状态一旦从 Pending 转为 Fulfilled 或 Rejected,就不可更改。

因为promise需要异步执行,所以用 queueMicrotask 或者 setTimeout 模拟微任务,主要是为了确保异步回调不会阻塞当前同步代码的执行。而且这里涉及一个知识,Promise.resolve().then是微任务,尽管setTimeout是宏任务,但也能够满足异步需求。

具体代码:

class MyPromise {
  constructor(executor) {
    this.state = 'pending'; // 初始状态
    this.value = undefined; // 成功时的值
    this.reason = undefined; // 失败时的原因
    this.onFulfilledCallbacks = []; // 成功回调队列
    this.onRejectedCallbacks = []; // 失败回调队列

    const resolve = (value) => {
      if (this.state === 'pending') {
        this.state = 'fulfilled';
        this.value = value;
        this.onFulfilledCallbacks.forEach((cb) => cb()); // 执行所有成功回调
      }
    };

    const reject = (reason) => {
      if (this.state === 'pending') {
        this.state = 'rejected';
        this.reason = reason;
        this.onRejectedCallbacks.forEach((cb) => cb()); // 执行所有失败回调
      }
    };

    try {
      executor(resolve, reject);
    } catch (err) {
      reject(err);
    }
  }

  then(onFulfilled, onRejected) {
    // 如果未传回调函数,给默认的透传函数
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : (value) => value;
    onRejected = typeof onRejected === 'function' ? onRejected : (reason) => { throw reason; };

    const promise2 = new MyPromise((resolve, reject) => {
      if (this.state === 'fulfilled') {
        // 保证异步执行
        queueMicrotask(() => {
          try {
            const x = onFulfilled(this.value);
            resolvePromise(promise2, x, resolve, reject);
          } catch (err) {
            reject(err);
          }
        });
      }

      if (this.state === 'rejected') {
        queueMicrotask(() => {
          try {
            const x = onRejected(this.reason);
            resolvePromise(promise2, x, resolve, reject);
          } catch (err) {
            reject(err);
          }
        });
      }

      if (this.state === 'pending') {
        this.onFulfilledCallbacks.push(() => {
          queueMicrotask(() => {
            try {
              const x = onFulfilled(this.value);
              resolvePromise(promise2, x, resolve, reject);
            } catch (err) {
              reject(err);
            }
          });
        });

        this.onRejectedCallbacks.push(() => {
          queueMicrotask(() => {
            try {
              const x = onRejected(this.reason);
              resolvePromise(promise2, x, resolve, reject);
            } catch (err) {
              reject(err);
            }
          });
        });
      }
    });

    return promise2;
  }

  catch(onRejected) {
    return this.then(null, onRejected);
  }

  finally(callback) {
    return this.then(
      (value) => MyPromise.resolve(callback()).then(() => value),
      (reason) => MyPromise.resolve(callback()).then(() => { throw reason; })
    );
  }

  // 静态 resolve 方法
  static resolve(value) {
    if (value instanceof MyPromise) {
      return value;
    }
    return new MyPromise((resolve) => resolve(value));
  }

  // 静态 reject 方法
  static reject(reason) {
    return new MyPromise((_, reject) => reject(reason));
  }
}

// 帮助处理 then 返回值
function resolvePromise(promise2, x, resolve, reject) {
  if (promise2 === x) {
    return reject(new TypeError('Chaining cycle detected for promise'));
  }

  if (x && (typeof x === 'object' || typeof x === 'function')) {
    let called = false;
    try {
      const then = x.then;
      if (typeof then === 'function') {
        then.call(
          x,
          (y) => {
            if (called) return;
            called = true;
            resolvePromise(promise2, y, resolve, reject);
          },
          (r) => {
            if (called) return;
            called = true;
            reject(r);
          }
        );
      } else {
        resolve(x);
      }
    } catch (err) {
      if (called) return;
      called = true;
      reject(err);
    }
  } else {
    resolve(x);
  }
}

module.exports = MyPromise;

测试:

// 测试
const promise = new MyPromise((resolve) => {
  setTimeout(() => resolve('Success!'), 1000);
});

promise
  .then((value) => {
    console.log('First:', value);
    return 'Next Step';
  })
  .then((value) => {
    console.log('Second:', value);
    return new MyPromise((resolve) => {
      setTimeout(() => resolve('Final Step'), 1000);
    });
  })
  .then((value) => console.log('Third:', value));


三、关于then的处理更简单的写法

then(onFulfilled, onRejected) {
    return new MyPromise((resolve, reject) => {
      const handleFulfilled = () => {
        try {
          const result = typeof onFulfilled === 'function' ? onFulfilled(this.value) : this.value;
          resolve(result);
        } catch (error) {
          reject(error);
        }
      };

      const handleRejected = () => {
        try {
          const result = typeof onRejected === 'function' ? onRejected(this.reason) : this.reason;
          resolve(result);
        } catch (error) {
          reject(error);
        }
      };

      if (this.state === 'fulfilled') {
        setTimeout(handleFulfilled, 0);
      } else if (this.state === 'rejected') {
        setTimeout(handleRejected, 0);
      } else {
        this.fulfillQueue.push(() => setTimeout(handleFulfilled, 0));
        this.rejectQueue.push(() => setTimeout(handleRejected, 0));
      }
    });
  }

四、finally:

finally(callback) {
    return this.then(
      (value) => MyPromise.resolve(callback()).then(() => value),
      (reason) => MyPromise.resolve(callback()).then(() => { throw reason; })
    );
  }

五、all

我们都知道,Promise.all的主要功能是接收一个可迭代对象(通常是数组),其中包含多个Promise或非 Promise值,返回一个新的Promise,然后其运行逻辑就是:

  • 如果所有 Promise 都成功,新的 Promise 会被 resolve,返回值是一个包含所有结果的数组
  • 如果其中任意一个 Promise 被 reject,新的 Promise 会立即被 reject,返回原因。

然后处理过程需要对传入的值是否是数组,是否是promise等进行判断,然后统计fulfillCount :

Promise.all = function (iterable) {
  // 将传入的可迭代对象转化为数组
  var tasks = Array.from(iterable);

  // 如果任务数组为空,直接返回一个 resolve 的 Promise,值为空数组
  if (tasks.length === 0) {
    return Promise.resolve([]);
  }

  // 如果所有任务都不是 Promise,则直接返回 resolve 的 Promise
  if (tasks.every(task => !(task instanceof Promise))) {
    return Promise.resolve(tasks);
  }

  // 返回一个新的 Promise,用于处理多个任务
  return new Promise((resolve, reject) => {
    var values = new Array(tasks.length).fill(null); // 用于存储任务结果的数组
    var fulfillCount = 0; // 用于统计已完成任务的数量

    // 遍历任务数组
    tasks.forEach((task, index, arr) => {
      if (task instanceof Promise) {
        // 如果任务是一个 Promise,处理其结果
        task.then(value => {
          fulfillCount++; // 成功的任务计数+1
          values[index] = value; // 将结果存储在正确的索引位置

          // 如果所有任务都完成,resolve 最终的结果数组
          if (fulfillCount === arr.length) {
            resolve(values);
          }
        }, reason => {
          // 如果任意任务失败,直接 reject
          reject(reason);
        });
      } else {
        // 如果任务不是 Promise,直接存储结果
        fulfillCount++;
        values[index] = task;
      }
    });
  });
};

最后再补充

这里有一个基于Promises/A+ 标准实现的例子,很规范,核心思想一致:GitHub - cumt-robin/promises-aplus-robin: a lightweight Promises/A+ implementation that is able to detect circular thenable chain.

主要了解核心,还有finally方法就差不多了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值