手写Promise

通过前面我们学习了promise的基本使用,现在通过手写来实现一个promise。

1.promise的基本结构

ini 复制代码
const PROMISE_PENDING="pending"
const PROMISE_FULFILLED="fulfilled"
const PROMISE_REJECTED="rejected"


class HYPromise {
  constructor(executor){
    this.status=PROMISE_PENDING;
    this.value=undefined;
    this.reason=undefined;

    const resolve=(value)=>{
      if(this.status===PROMISE_PENDING){
        this.status=PROMISE_FULFILLED;
        this.value=value;
        console.log('fulfilled',value);
      }
    }

    const reject=(reason)=>{
      if(this.status===PROMISE_PENDING){
        this.status=PROMISE_REJECTED;
        this.reason=reason;
        console.log('rejected',reason);
      }
    }
    executor(resolve,reject)
  }
}

const promise = new MYPromise((resolve,reject)=>{
  resolve('111');
  reject('222');
})

//'fulfilled',111

在这个代码中,我们先定义了一个HYPromise,然后通过new HYPromise创建一个新的promise。在创建的新的promise的时候他会立即执行constructor构造方法,用executor来接收传递过来的方法。

当代码运行时,首先会将此时的状态变为pending状态,然后判断是先执行了resolve方法还是reject方法,此段代码先执行的是resolve方法,即会调用HYPromise中的resolve,此时的状态就会变为fulfilled。但再进行下面的reject方法时,由于状态不是pending,则不会执行,从而实现了状态的简单管理。

2.promise的then方法实现

ini 复制代码
const PROMISE_STATUS_PENDING = 'pending';
const PROMISE_STATUS_FULFILLED = 'fulfilled';
const PROMISE_STATUS_REJECTED = 'rejected';

class HYPromise {
  constructor(executor) {
    this.status = PROMISE_STATUS_PENDING;
    this.value = undefined;
    this.reason = undefined;

    const resolve = (value) => {
      if (this.status === PROMISE_STATUS_PENDING) {
        this.status = PROMISE_STATUS_FULFILLED;
        queueMicrotask(() => {
          this.value = value;
          this.onFulfilled(this.value);
        });
      }
    };

    const reject = (reason) => {
      if (this.status === PROMISE_STATUS_PENDING) {
        this.status = PROMISE_STATUS_REJECTED;
        queueMicrotask(() => {
          this.reason = reason;
          this.onRejected(this.reason);
        });
      }
    };

    executor(resolve, reject);
  }

  then(onFulfilled, onRejected) {
    this.onFulfilled = onFulfilled;
    this.onRejected = onRejected;
  }
}

const promise = new HYPromise((resolve, reject) => {
  console.log('状态pending');
  resolve(1111);
});

promise.then(
  (res) => {
    console.log('res1:', res);
  },
  (err) => {
    console.log('err:', err);
  }
);

//打印结果
//'res1',111

当创建的 HYPromise 实例调用 then 方法时,它会接收两个回调函数作为参数,并分别赋值给 this.onFulfilled 和 this.onRejected。

queueMicrotask 方法用于在当前事件循环的末尾执行一个微任务。在这个例子中,当 resolve 或 reject 被调用时,我们通过 queueMicrotask 将回调函数(onFulfilled 或 onRejected)的执行推迟到当前宏任务完成后。这样做的好处是,它允许我们在事件循环的当前迭代中,在可能的其他代码执行完毕后,再执行这些回调函数。如果没有queueMicrotask方法,则此代码会报错。

但是这个方法还存在一个问题,就是如果连续调用了then方法,则第一个then方法不会执行,因为保存的第二次保存的this.onFulfilled会覆盖第一次保存的this.onFulfilled。接下来将进行then()方法的优化。

3.promise的then方法的优化

kotlin 复制代码
// ES6 ES2015
// https://promisesaplus.com/
const PROMISE_STATUS_PENDING = 'pending'
const PROMISE_STATUS_FULFILLED = 'fulfilled'
const PROMISE_STATUS_REJECTED = 'rejected'

// 工具函数
function execFunctionWithCatchError(execFn, value, resolve, reject) {
  try {
    const result = execFn(value)
    resolve(result)
  } catch(err) {
    reject(err)
  }
}

class HYPromise {
  constructor(executor) {
    this.status = PROMISE_STATUS_PENDING
    this.value = undefined
    this.reason = undefined
    this.onFulfilledFns = []
    this.onRejectedFns = []

    const resolve = (value) => {
      if (this.status === PROMISE_STATUS_PENDING) {
        // 添加微任务
        queueMicrotask(() => {
          if (this.status !== PROMISE_STATUS_PENDING) return
          this.status = PROMISE_STATUS_FULFILLED
          this.value = value
          this.onFulfilledFns.forEach(fn => {
            fn(this.value)
          })
        });
      }
    }

    const reject = (reason) => {
      if (this.status === PROMISE_STATUS_PENDING) {
        // 添加微任务
        queueMicrotask(() => {
          if (this.status !== PROMISE_STATUS_PENDING) return
          this.status = PROMISE_STATUS_REJECTED
          this.reason = reason
          this.onRejectedFns.forEach(fn => {
            fn(this.reason)
          })
        })
      }
    }

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

  then(onFulfilled, onRejected) {
    return new HYPromise((resolve, reject) => {
      // 1.如果在then调用的时候, 状态已经确定下来
      if (this.status === PROMISE_STATUS_FULFILLED && onFulfilled) {
        execFunctionWithCatchError(onFulfilled, this.value, resolve, reject)
      }
      if (this.status === PROMISE_STATUS_REJECTED && onRejected) {
        execFunctionWithCatchError(onRejected, this.reason, resolve, reject)
      }

      // 2.将成功回调和失败的回调放到数组中
      if (this.status === PROMISE_STATUS_PENDING) {
        this.onFulfilledFns.push(() => {
          execFunctionWithCatchError(onFulfilled, this.value, resolve, reject)
        })
        this.onRejectedFns.push(() => {
          execFunctionWithCatchError(onRejected, this.reason, resolve, reject)
        })
      }
    })
  }
}

const promise = new HYPromise((resolve, reject) => {
  console.log("状态pending")
  resolve(1111)
  // reject(2222)
  // throw new Error("executor error message")
})

// 调用then方法多次调用
promise.then(res => {
  console.log("res1:", res)
  return "aaaa"
  // throw new Error("err message")
}, err => {
  console.log("err1:", err)
  return "bbbbb"
  // throw new Error("err message")
}).then(res => {
  console.log("res2:", res)
}, err => {
  console.log("err2:", err)
})

//打印结果
状态pending
res1: 1111
res2: aaaa

在这里我们创建了2个数组,一个是onFulfilledFns,即调用then()方法成功回调的集合,还有一个onRejectedFns即失败的回调。

此段代码通过new HYPromise,先执行了resolve(),然后因为有微任务函数queueMicrotask(),它会先执行宏任务函数,最后在执行微任务。后面又调用了then()方法,此时的status是还没有确定的,还是pending状态,然后就可以将回调函数保存在对应的数组中。当执行完这些then方法后,就执行微任务queueMicrotask函数,此时status为fulfilled,通过this.value = value保存resolve传来的参数,最后在foreach循环执行。

在这里我们的then()方法返回的是一个HYPromise,如果不是传的promise对象,在继续通过.then()方法会报undefined。

另外根据传统的promise,在调用reject()方法后,它会先打印一个err,然后调用then方法时,他的状态会变为pending.

这里的execFunctionWithCatchError,我们封装了一个方法,把promise的回调方法传过去(onFulfilled或者onRejected),然后传值,还有resolve,reject方法,这个execFunctionWithCatchError通过try-catch去拦截抛出异常,如果抛出了异常,就继续调用reject方法。

当多次调用then方法时,它就能完成正常promise调用。

4.promise-catch方法设计

kotlin 复制代码
class HYPromise {
  ......
  catch(onRejected) {
   return this.then(undefined, onRejected)
  }
}

这个catch方法我们可以直接通过调用then方法,然后传入一个reject方法。但是这个实现还存在一些问题,当调用catch,传入then方法时,因为onFulfilled是空的,所以会有报错。所以我们修改一下then方法。加入判空操作。

javascript 复制代码
class HYPromise {
  ......
   then(onFulfilled, onRejected) {
     ...
    return new HYPromise((resolve, reject) => {
      ...
      if (this.status === PROMISE_STATUS_PENDING) {
        if (onFulfilled) this.onFulfilledFns.push(() => {
          execFunctionWithCatchError(onFulfilled, this.value, resolve, reject)
        })
        if (onRejected) this.onRejectedFns.push(() => {
          execFunctionWithCatchError(onRejected, this.reason, resolve, reject)
        })
      }
    })
  }
}
javascript 复制代码
const promise = new HYPromise((resolve, reject) => {
  console.log('状态pending');
  // resolve(1111) // resolved/fulfilled
  reject(2222);
});

promise
  .then((res) => {
    console.log('res:', res);
  })
  .catch((err) => {
    console.log('err:', err);
  });

当我们这样调用时,发现并没有打印err,没有调用catch方法。

因为调用的reject方法是通过传入then方法的第二个回调参数,但是这个then方法并没有传,即为undefined,所以就没有传入onRejectedFns中。而调用的catach方法是第二个的promise的catch方法,但是上一步的promise对象并没有被创建,因此不会执行。为了能直接执行catch方法,我们要在then方法在做修改。

ini 复制代码
  then(onFulfilled, onRejected) {
    const defaultOnRejected = (err) => {
      throw err;
    };
    onRejected = onRejected || defaultOnRejected;
  }

在then方法开始的时候做一个判断,如果没有传递第二个参数,则抛出一个异常。则this.onRejectedFns数组里会生成一个函数,继续执行就会创建一个新的promise,然后再通过promise去调用catch方法。

5.promise-finally方法设计

scss 复制代码
class HYPromise{
  finally(onFinally) {
    this.then(() => {
      onFinally()
    }, () => {
      onFinally()
    })
  }
}

这个方法就是不管是成功还是失败,都会调用onFinally()回调函数的代码。

但是会存在一个问题,如果在这之前调用了catch方法。因为我们在上面的catch方法中传入then方法的第一个参数是undefined。因此也会存在调用undefined.finally()。因此我们需要在then方法中,再加一个判断返回传入的值。

ini 复制代码
  then(onFulfilled, onRejected) {
    const defaultOnFulfilled = value => { return value }
    onFulfilled = onFulfilled || defaultOnFulfilled
  }

6.promise-resolve-reject类方法

在promise中,可以直接调用resolve和reject,即这是个静态方法。

javascript 复制代码
class HYPromise{
  static resolve(value) {
    return new HYPromise((resolve) => resolve(value))
  }

  static reject(reason) {
    return new HYPromise((resolve, reject) => reject(reason))
  }
}


HYPromise.resolve("Hello World").then(res => {
  console.log("res:", res)
})

HYPromise.reject("Error Message").catch(err => {
  console.log("err:", err)
})


// 结果
res: Hello World
err: Error Message

7.promise-all-allSettled类方法

首先all和allSettled方法都是可以直接调用的,即都是promise的类方法。另外all方法接收一个 Promise 对象的数组作为参数,并返回一个新的 Promise 实例,这个新的 Promise 实例在数组中的所有 Promise 都成功解析时才会解析,其解析值为所有 Promise 解析值的数组。如果数组中的任何一个 Promise 被拒绝,则新的 Promise 会立即被拒绝,并返回err结果。allSteeled方法也是接收一个Promise数组,会执行全部方法,并且返回使用的status和value。

scss 复制代码
class HYPromise{
  static all(promises) {
    // 问题关键: 什么时候要执行resolve, 什么时候要执行reject
    return new HYPromise((resolve, reject) => {
      const values = []
      promises.forEach(promise => {
        promise.then(res => {
          values.push(res)
          if (values.length === promises.length) {
            resolve(values)
          }
        }, err => {
          reject(err)
        })
      })
    })
  }

  static allSettled(promises) {
    return new HYPromise((resolve) => {
      const results = []
      promises.forEach(promise => {
        promise.then(res => {
          results.push({ status: PROMISE_STATUS_FULFILLED, value: res})
          if (results.length === promises.length) {
            resolve(results)
          }
        }, err => {
          results.push({ status: PROMISE_STATUS_REJECTED, value: err})
          if (results.length === promises.length) {
            resolve(results)
          }
        })
      })
    })
  }
}
}



const p1 = new Promise((resolve) => {
  setTimeout(() => { resolve(1111) }, 1000)
})
const p2 = new Promise((resolve, reject) => {
  setTimeout(() => { reject(2222) }, 2000)
})
const p3 = new Promise((resolve) => {
  setTimeout(() => { resolve(3333) }, 3000)
})
// HYPromise.all([p1, p2, p3]).then(res => {
//   console.log(res)
// }).catch(err => {
//   console.log(err)
// })

HYPromise.allSettled([p1, p2, p3]).then(res => {
  console.log(res)
})

//all方法打印
2222

//allSettled打印
[  { status: 'fulfilled', value: 1111 },  { status: 'rejected', value: 2222 },  { status: 'fulfilled', value: 3333 }]

这里的all方法里这个判断if (values.length === promises.length) {resolve(values)};是假如全部的promise返回都为成功的话,就执行resolve。如果不是就遇到reject就返回错误信息。

8.promise-race-any类方法

race,any和all,allSettled都一样是类方法。

race是只要有一个Promise实例有结果了,不管是成功还是异常都直接进行打印。

any的话跟race也差不多只是它必须得有一个成功的结果才返回,如果全是错误它就会合并错误并返回。

javascript 复制代码
class HYPromise{
  ...
  static race(promises) {
    return new HYPromise((resolve, reject) => {
      promises.forEach(promise => {
        // promise.then(res => {
        //   resolve(res)
        // }, err => {
        //   reject(err)
        // })
        promise.then(resolve, reject)
      })
    })
  } 

  static any(promises) {
    // resolve必须等到有一个成功的结果
    // reject所有的都失败才执行reject
    const reasons = []
    return new HYPromise((resolve, reject) => {
      promises.forEach(promise => {
        promise.then(resolve, err => {
          reasons.push(err)
          if (reasons.length === promises.length) {
            reject(new AggregateError(reasons))
          }
        })
      })
    })
  }
}


const p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(1111);
  }, 3000);
});
const p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject(2222);
  }, 2000);
});
const p3 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject(3333);
  }, 3000);
});

// HYPromise.race([p1, p2, p3]).then(res => {
//   console.log("res:", res)
// }).catch(err => {
//   console.log("err:", err)
// })

HYPromise.any([p1, p2, p3])
  .then((res) => {
    console.log('res:', res);
  })
  .catch((err) => {
    console.log('err:', err.errors);
  });

//race方法打印


//any方法打印
res: 1111
(如果第一个是reject(1111))
err: [ 2222, 1111, 3333 ]

//race方法打印
err: 2222

到这里我们实现的基本差不多了,在promise中基本的方法都是围绕then方法的实现。

9.完整代码如下

scss 复制代码
// ES6 ES2015
// https://promisesaplus.com/
const PROMISE_STATUS_PENDING = 'pending';
const PROMISE_STATUS_FULFILLED = 'fulfilled';
const PROMISE_STATUS_REJECTED = 'rejected';

// 工具函数
function execFunctionWithCatchError(execFn, value, resolve, reject) {
  try {
    const result = execFn(value);
    resolve(result);
  } catch (err) {
    reject(err);
  }
}

class HYPromise {
  constructor(executor) {
    this.status = PROMISE_STATUS_PENDING;
    this.value = undefined;
    this.reason = undefined;
    this.onFulfilledFns = [];
    this.onRejectedFns = [];

    const resolve = (value) => {
      if (this.status === PROMISE_STATUS_PENDING) {
        // 添加微任务
        queueMicrotask(() => {
          if (this.status !== PROMISE_STATUS_PENDING) return;
          this.status = PROMISE_STATUS_FULFILLED;
          this.value = value;
          this.onFulfilledFns.forEach((fn) => {
            fn(this.value);
          });
        });
      }
    };

    const reject = (reason) => {
      if (this.status === PROMISE_STATUS_PENDING) {
        // 添加微任务
        queueMicrotask(() => {
          if (this.status !== PROMISE_STATUS_PENDING) return;
          this.status = PROMISE_STATUS_REJECTED;
          this.reason = reason;
          this.onRejectedFns.forEach((fn) => {
            fn(this.reason);
          });
        });
      }
    };

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

  then(onFulfilled, onRejected) {
    const defaultOnRejected = (err) => {
      throw err;
    };
    onRejected = onRejected || defaultOnRejected;

    const defaultOnFulfilled = (value) => {
      return value;
    };
    onFulfilled = onFulfilled || defaultOnFulfilled;

    return new HYPromise((resolve, reject) => {
      // 1.如果在then调用的时候, 状态已经确定下来
      if (this.status === PROMISE_STATUS_FULFILLED && onFulfilled) {
        execFunctionWithCatchError(onFulfilled, this.value, resolve, reject);
      }
      if (this.status === PROMISE_STATUS_REJECTED && onRejected) {
        execFunctionWithCatchError(onRejected, this.reason, resolve, reject);
      }

      // 2.将成功回调和失败的回调放到数组中
      if (this.status === PROMISE_STATUS_PENDING) {
        if (onFulfilled)
          this.onFulfilledFns.push(() => {
            execFunctionWithCatchError(
              onFulfilled,
              this.value,
              resolve,
              reject
            );
          });
        if (onRejected)
          this.onRejectedFns.push(() => {
            execFunctionWithCatchError(
              onRejected,
              this.reason,
              resolve,
              reject
            );
          });
      }
    });
  }

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

  finally(onFinally) {
    this.then(
      () => {
        onFinally();
      },
      () => {
        onFinally();
      }
    );
  }

  static resolve(value) {
    return new HYPromise((resolve) => resolve(value));
  }

  static reject(reason) {
    return new HYPromise((resolve, reject) => reject(reason));
  }

  static all(promises) {
    // 问题关键: 什么时候要执行resolve, 什么时候要执行reject
    return new HYPromise((resolve, reject) => {
      const values = [];
      promises.forEach((promise) => {
        promise.then(
          (res) => {
            values.push(res);
            if (values.length === promises.length) {
              resolve(values);
            }
          },
          (err) => {
            reject(err);
          }
        );
      });
    });
  }

  static allSettled(promises) {
    return new HYPromise((resolve) => {
      const results = [];
      promises.forEach((promise) => {
        promise.then(
          (res) => {
            results.push({ status: PROMISE_STATUS_FULFILLED, value: res });
            if (results.length === promises.length) {
              resolve(results);
            }
          },
          (err) => {
            results.push({ status: PROMISE_STATUS_REJECTED, value: err });
            if (results.length === promises.length) {
              resolve(results);
            }
          }
        );
      });
    });
  }

  static race(promises) {
    return new HYPromise((resolve, reject) => {
      promises.forEach((promise) => {
        // promise.then(res => {
        //   resolve(res)
        // }, err => {
        //   reject(err)
        // })
        promise.then(resolve, reject);
      });
    });
  }

  static any(promises) {
    // resolve必须等到有一个成功的结果
    // reject所有的都失败才执行reject
    const reasons = [];
    return new HYPromise((resolve, reject) => {
      promises.forEach((promise) => {
        promise.then(resolve, (err) => {
          reasons.push(err);
          if (reasons.length === promises.length) {
            reject(new AggregateError(reasons));
          }
        });
      });
    });
  }
}

const p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(1111);
  }, 3000);
});
const p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject(2222);
  }, 2000);
});
const p3 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject(3333);
  }, 3000);
});

HYPromise.race([p1, p2, p3])
  .then((res) => {
    console.log('res:', res);
  })
  .catch((err) => {
    console.log('err:', err);
  });

// HYPromise.any([p1, p2, p3])
//   .then((res) => {
//     console.log('res:', res);
//   })
//   .catch((err) => {
//     console.log('err:', err.errors);
//   });
相关推荐
腾讯TNTWeb前端团队44 分钟前
helux v5 发布了,像pinia一样优雅地管理你的react状态吧
前端·javascript·react.js
范文杰4 小时前
AI 时代如何更高效开发前端组件?21st.dev 给了一种答案
前端·ai编程
拉不动的猪4 小时前
刷刷题50(常见的js数据通信与渲染问题)
前端·javascript·面试
拉不动的猪4 小时前
JS多线程Webworks中的几种实战场景演示
前端·javascript·面试
FreeCultureBoy5 小时前
macOS 命令行 原生挂载 webdav 方法
前端
uhakadotcom6 小时前
Astro 框架:快速构建内容驱动型网站的利器
前端·javascript·面试
uhakadotcom6 小时前
了解Nest.js和Next.js:如何选择合适的框架
前端·javascript·面试
uhakadotcom6 小时前
React与Next.js:基础知识及应用场景
前端·面试·github
uhakadotcom6 小时前
Remix 框架:性能与易用性的完美结合
前端·javascript·面试
uhakadotcom6 小时前
Node.js 包管理器:npm vs pnpm
前端·javascript·面试