手写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);
//   });
相关推荐
也无晴也无风雨1 小时前
深入剖析输入URL按下回车,浏览器做了什么
前端·后端·计算机网络
Martin -Tang1 小时前
Vue 3 中,ref 和 reactive的区别
前端·javascript·vue.js
FakeOccupational3 小时前
nodejs 020: React语法规则 props和state
前端·javascript·react.js
放逐者-保持本心,方可放逐3 小时前
react 组件应用
开发语言·前端·javascript·react.js·前端框架
曹天骄4 小时前
next中服务端组件共享接口数据
前端·javascript·react.js
阮少年、4 小时前
java后台生成模拟聊天截图并返回给前端
java·开发语言·前端
郝晨妤6 小时前
鸿蒙ArkTS和TS有什么区别?
前端·javascript·typescript·鸿蒙
AvatarGiser6 小时前
《ElementPlus 与 ElementUI 差异集合》Icon 图标 More 差异说明
前端·vue.js·elementui
喝旺仔la6 小时前
vue的样式知识点
前端·javascript·vue.js
别忘了微笑_cuicui6 小时前
elementUI中2个日期组件实现开始时间、结束时间(禁用日期面板、控制开始时间不能超过结束时间的时分秒)实现方案
前端·javascript·elementui