Promise及其API源码的实现思考过程

1、Promise&&then

我们知道,promise是为了解决异步回调地狱而出现的新的解决方案,它是主要特点是可以通过then拿到异步的结果,then又能返回新的异步结果,形成链式调用

javascript 复制代码
new Promise((resolve,reject)=>{
resolve('结果')
}).then(res=>{
return res
}).then(...)

我们也知道,在promise中有三个状态,pending进行中,fulfilled已完成,rejected已失败,且一旦pending改变,不会再被更改。

所以先根据以上信息,实现:可以通过then链式调用

ini 复制代码
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class myPromise {
    constructor(executor) {
        this.status = PENDING;
        this.value = undefined;
        this.reason = undefined;
        // this.onFulfilledCbs = [];
        // this.onRejectedCbs = [];

        const resolve = (value) => {
            if (this.status === PENDING) {
                this.status = FULFILLED;
                this.value = value
                // this.onFulfilledCbs.forEach(cb => cb(value))
            }
        }
        const reject = (reason) => {
            if (this.status === PENDING) {
                this.status = REJECTED;
                this.reason = reason
                // this.onRejectedCbs.forEach(cb => cb(reason))
            }
        }

        executor(resolve, reject)
    }
    then(onFulfilled, onRejected) {
        return new myPromise((resolve, reject) => {
            let result = undefined;
            if (this.status === FULFILLED) {
                result = onFulfilled(this.value)
                resolve(result)
            }
            if (this.status === REJECTED) {
                result = onRejected(this.reason)
                reject(result)
            }
            // if (this.status === PENDING) {
            //     this.onFulfilledCbs.push(onFulfilled)
            //     this.onRejectedCbs.push(onRejected)
            // }
        })
    }
}
new myPromise((resolve, reject) => {
    // setTimeout(() => {
        resolve(111)
    // }, 1000)
}).then(res => {
    console.log('res', res)
})

根据以上代码,在then中再return promise,实现了简单的链式调用,且需要setTimeout模拟异步,在then中拿到异步操作返回的结果,因为是异步的,当执行then时status还是pending,需要把成功和失败回调存储,等到resolve或reject时拿出来执行。(这部分代码实现把以上注释部分取消即可。)

根据promise的约定,resolve接收的参数可以有4种,所以需要兼容一下,目前只有一种就是基础值,还有promise、thenable对象、空,且resolve出的值不能是promise自身,否则会造成无限循环引用。

同时用setTimeout将异步模拟一下,有如以下代码:

ini 复制代码
// promise有三种状态,分别是pending.FULFILLED ,rejected,pending是初始状态,FULFILLED 是成功状态,rejected是失败状态
// 状态一旦改变,不会再变
const PENDING = "pending";
const FULFILLED = "FULFILLED ";
const REJECTED = "rejected";
class Promise1 {
  constructor(excutor) {
    // 其实resolve,reject这两个函数不是他们传来的,只是他们调用的内部的方法,它们传来的只是值
    this.status = PENDING;
    this.value = undefined;
    this.error = undefined;
    this.onFulfilledCallbacks = []; // 存储then方法中onFULFILLED 的回调函数
    this.onRejectedCallbacks = []; // 存储then方法中onRejected的回调函数

    const resolve = (value) => {
      if (this.status === PENDING) {
        this.status = FULFILLED;
        this.value = value;
        this.onFulfilledCallbacks.forEach((callback) => callback());
      }
    };
    const reject = (error) => {
      if (this.status === PENDING) {
        this.status = REJECTED;
        this.error = error;
        this.onRejectedCallbacks.forEach((callback) => callback());
      }
    };
    try {
      excutor(resolve, reject);
    } catch (error) {
      reject(error);
    }
  }
  then(onFulfilled, onRejected) {
    // 处理onFULFILLED 不是函数的情况
    onFulfilled =
      typeof onFulfilled === "function" ? onFulfilled : (value) => value;
    // 处理onRejected不是函数的情况
    onRejected =
      typeof onRejected === "function"
        ? onRejected
        : (error) => {
            throw error;
          };
    // then最终还是会return一个promise
    const promise = new Promise((resolve, reject) => {
      const handleFulfilled = () => {
        try {
          const result = onFulfilled(this.value);
          resolvePromise(promise, result, resolve, reject);
        } catch (error) {
          reject(error);
        }
      };

      const handleRejected = () => {
        try {
          const result = onRejected(this.error);
          resolvePromise(promise, result, resolve, reject);
        } catch (error) {
          reject(error);
        }
      };

      if (this.status === FULFILLED) {
        // 为什么这里需要setTimeout,因为要模拟promise是异步的
        setTimeout(handleFulfilled, 0);
      } else if (this.status === REJECTED) {
        setTimeout(handleRejected, 0);
      } else {
        this.onFulfilledCallbacks.push(() => setTimeout(handleFulfilled, 0));
        this.onRejectedCallbacks.push(() => setTimeout(handleRejected, 0));
      }
    });
    return promise;
  }
}
function resolvePromise(promise, result, resolve, reject) {
  // 如果promise和result是同一个对象,则抛出TypeError,
  if (promise === result) {
    return reject(new TypeError("Chaining cycle detected for promise"));
  }
  // 如果result是thenable对象
  if (result && (typeof result === "object" || typeof result === "function")) {
    try {
      const then = result.then;
      if (typeof then === "function") {
        // then就是接收两个参数:resolve或reject,然后出结果的函数,直接执行它,但注意this指向,并传参
        then.call(
          this,
          (value) => {
            resolvePromise(promise, value, resolve, reject);
          },
          (reason) => reject(reason)
        );
      } else {
        resolve(result);
      }
    } catch (error) {
      reject(error);
    }
  } else {
    // 否则直接调用resolve
    resolve(result);
  }
}

2、catch

then后面除了可以跟着then,也可以通过catch方法捕获错误,它等同于then中的第二个参数,所以通过调用then,将catch中的回调函数传给then第二个参数即可。

kotlin 复制代码
// 声明在Promise类中
catch(cb){
  return this.then(_,cb)
}

3、finally

Promise的实例方法还有一个叫finally,是不管成功还是失败都会执行的,还是用then方法返回结果,不管成功还是失败结果都resolve返回一个promise,从而catch后面也可继续链式调用。then接收两个参数,成功后的回调,这个回调的参数是成功返回的值,所以finally若想

javascript 复制代码
// 声明在Promise类中
finally(cb){
    return this.then(
        value=>Promise1.resolve(cb()).then(()=>value)
        reason=>Promise1.resolve(cb()).then(()=>reason)
)}

4、resolve

在Promise类上,有resolve,reject,race,all,allSettled等类方法,接下来一一演示,Promise.resolve(1)就是等同于new Resolve(()=>{resolve(1)}),既然如此直接返回后者,再加个判断,如果不是promise才需要包装,是promise直接返回

scss 复制代码
static resolve(value){
 if(value instanceof Promise1){
 return value;
 }
 return new Promise1(()=>resolve(value))
}

5、reject

reject同理可得,Promise.reject('err')就是等同于new Promise1(()=>{reject('err')})

javascript 复制代码
static reject(reason){
return new Promise1((_,reject)=>reject(reason))
}

6、race

race、all、allSettled都是为了解决多个promise同时执行的,race是当其中一个先执行完毕,多个promise实例的执行结果就是这个最先完成的状态。 既然是多个,那参数就是数组,我们只需去遍历,最先执行resolve的,也就是最先改变状态的,后续即使继续遍历,也不再改变状态。

怎么知道它何时resolve呢,因为我们知道then中的回调是在成功时调用的,所以通过Promise.resolve包装,在then中去resolve实例

javascript 复制代码
static race(arr){
 return new Promise1((resolve,reject)=>{
  arr.forEach(pro=>{
   Promise1.resolve(pro).then((value)=>{
     resolve(pro)
   },reason=>{
     reject(reason)
   })
  })
 })
}

7、all

all是多个promise实例,只要有一个失败,结果就是失败,全部成功才算成功。所以要计算成功的次数,数组长度等于成功的个数后,再resolve结果数组。

ini 复制代码
  static all(arr) {
    return new Promise1((resolve, reject) => {
      let res = [];
      let completeCount = 0;
      if (arr.length === 0) {
        resolve(res);
        return;
      }
      arr.forEach((pro) => {
        Promise1.resolve(pro).then(
          (value) => {
            res[completeCount] = value;
            completeCount++;
            if (completeCount === arr.length) {
              resolve(res);
            }
          },
          (err) => {
            reject(err);
          }
        );
      });
    });
  }
}

8、allSettled

allSettled是不管成功还是失败,都会返回结果的。注意不管当前是成功还是失败,当执行到最后一个时,都是resolve出数组结果,且结果数组对象是由status和结果值组成的,如果成功status='fulfilled',失败status='rejected'

ini 复制代码
  static allSettled(promises) {
    return new CustomPromise((resolve, reject) => {
      const results = [];
      let completedCount = 0;
      if (promises.length === 0) {
        resolve(results);
        return;
      }
      promises.forEach((promise, index) => {
        CustomPromise.resolve(promise).then(
          (value) => {
            results[index] = {
              status: "fulfilled",
              value,
            };
            completedCount++;
            if (completedCount === promises.length) {
              resolve(results);
            }
          },
          (reason) => {
            results[index] = {
              status: "rejected",
              reason,
            };
            completedCount++;
            if (completedCount === promises.length) {
              resolve(results);
            }
          }
        );
      });
    });
  }

根据以上,留一个作业,any方法,只要有一个成功,包装实例返回结果fulfilled,或者全部都失败,结果才会是rejected

相关推荐
开开心心就好43 分钟前
电脑息屏工具,一键黑屏超方便
开发语言·javascript·电脑·scala·erlang·perl
江号软件分享44 分钟前
有效保障隐私,如何安全地擦除电脑上的敏感数据
前端
web守墓人2 小时前
【前端】ikun-markdown: 纯js实现markdown到富文本html的转换库
前端·javascript·html
Savior`L2 小时前
CSS知识复习5
前端·css
许白掰2 小时前
Linux入门篇学习——Linux 工具之 make 工具和 makefile 文件
linux·运维·服务器·前端·学习·编辑器
中微子6 小时前
🔥 React Context 面试必考!从源码到实战的完整攻略 | 99%的人都不知道的性能陷阱
前端·react.js
秋田君7 小时前
深入理解JavaScript设计模式之命令模式
javascript·设计模式·命令模式
中微子7 小时前
React 状态管理 源码深度解析
前端·react.js
风吹落叶花飘荡8 小时前
2025 Next.js项目提前编译并在服务器
服务器·开发语言·javascript
加减法原则8 小时前
Vue3 组合式函数:让你的代码复用如丝般顺滑
前端·vue.js