手写Promise(保姆级教程)

简介: promise一直是js异步方面考察的一个重点,开发者不仅要熟练运用它的各个api。而且还要熟知原理,最好自己能动手实现一个promise。

今天笔者不再讲promise使用方面的知识了,小伙伴们可以自行查阅相关文档,或者看笔者前面写的js异步编程。今天我们的重点是自己动手实现一个promise,并且完全符合Promise A+规范

好啦,咱们开始吧。

基础版

我们知道promise的构造方法接收一个executor(resolve, reject)函数,在new Promise()时就会立刻执行这个executor回调。

并且对于then里面的回调方法,不是立即执行的,而是异步执行的。属于微任务。所以下面我们使用两个队列来分别存储各自回调函数。

class MyPromise {
  // 定义两个数组,存储回调
  resolveQueue = [];
  rejectQueue = [];

  constructor(executor) {
    // 实现resolve
    const resolve = (val) => {
      if (this.resolveQueue.length) {
        const callback = this.resolveQueue.shift();
        callback(val);
      }
    };

    // 实现reject
    const reject = (val) => {
      if (this.rejectQueue.length) {
        const callback = this.rejectQueue.shift();
        callback(val);
      }
    };

    // new Promise()时立即执行executor,并传入resolve和reject
    executor(resolve, reject);
  }

  // 存储回调
  then(resolveFun, rejectFun) {
    this.resolveQueue.push(resolveFun);
    this.rejectQueue.push(rejectFun);
  }
}

测试,先输出同步代码 然后再输出 success

const mp1 = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve("success");
  });
});

mp1.then((res) => {
  console.log(res); // success
});

console.log("同步代码");

image.png

到这我们基础版的Promise就实现好了,但是它是不符合Promise A+规范的。

那什么是Promise A+规范呢?

Promise A+规范

Promise/A+的规范比较长,这里只总结两条核心规则:

  1. Promise本质是一个状态机,且状态只能为以下三种:Pending(等待态)Fulfilled(执行态)Rejected(拒绝态),状态的变更是单向的,只能从Pending -> FulfilledPending -> Rejected,状态变更不可逆
  2. then方法接收两个可选参数,分别对应状态改变时触发的回调。then 方法可以被同一个 promise 调用多次。then方法返回一个promise可以链式调用。

对于第一条,是肯定不符合的,因为我们写的promise里面根本就没定义这三种状态。

对于第二条,同一个promise 能被调用多次,我们来测试一下。

const mp1 = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve("success");
  });
});

mp1.then((res) => {
  console.log(res);
});

mp1.then((res) => {
  console.log(res);
});

对于同一个promise可以被多次调用,发现我们的promise只能执行一次。😢

image.png

对于then方法返回一个promise可以链式调用。我们来测试一下。

const mp1 = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve("success");
  });
});

mp1
  .then((res) => {
    console.log(res);
  })
  .then((res) => {
    console.log(res);
  });

居然直接报错了。😦

image.png

好吧,接下来我们根据规范,来完善一下我们上面的代码。

首先我们来实现三种状态

实现三种状态

// 定义Promise/A+规范的三种状态
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";

class MyPromise {
  // 定义两个数组,存储回调
  resolveQueue = [];
  rejectQueue = [];
  status = PENDING;

  constructor(executor) {
    // 实现resolve
    const resolve = (val) => {
      // 对应规范中的"状态只能由pending到fulfilled或rejected"
      if (this.status !== PENDING) {
        return;
      }
      // 变更状态
      this.status = FULFILLED;
      // 执行回调
      if (this.resolveQueue.length) {
        const callback = this.resolveQueue.shift();
        callback(val);
      }
    };

    // 实现reject
    const reject = (val) => {
      // 对应规范中的"状态只能由pending到fulfilled或rejected"
      if (this.status !== PENDING) {
        return;
      }
      // 变更状态
      this.status = REJECTED;
      // 执行回调
      if (this.rejectQueue.length) {
        const callback = this.rejectQueue.shift();
        callback(val);
      }
    };

    // new Promise()时立即执行executor,并传入resolve和reject函数供外部使用
    executor(resolve, reject);
  }

  // 存储回调
  then(resolveFun, rejectFun) {
    this.resolveQueue.push(resolveFun);
    this.rejectQueue.push(rejectFun);
  }
}

这样我们的promise就有三种状态啦。😀

实现多次调用

想要实现多次调用其实改一个地方就可以了。只需要将构造函数里我们执行回调的地方if判断改成while就可以了。因为if只会调用一次,只会取回调数组中的第一个回调执行,而while可以将整个回调数组清空。

// ...

constructor(executor) {
  // 实现resolve
  const resolve = (val) => {
    // 对应规范中的"状态只能由pending到fulfilled或rejected"
    if (this.status !== PENDING) {
      return;
    }
    // 变更状态
    this.status = FULFILLED;
    while (this.resolveQueue.length) {
      const callback = this.resolveQueue.shift();
      callback(val);
    }
  };

  // 实现reject
  const reject = (val) => {
    // 对应规范中的"状态只能由pending到fulfilled或rejected"
    if (this.status !== PENDING) {
      return;
    }
    // 变更状态
    this.status = REJECTED;
    while (this.rejectQueue.length) {
      const callback = this.rejectQueue.shift();
      callback(val);
    }
  };

  // new Promise()时立即执行executor,并传入resolve和reject
  executor(resolve, reject);
}

// ...

我们来测试下

const mp1 = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve("success");
  });
});

mp1.then((res) => {
  console.log(res);
});

mp1.then((res) => {
  console.log(res);
});

这下就可以啦,调用几次执行几次。😀

image.png

实现链式调用

要想实现链式调用,那肯定是then()方法要返回一个promise,因为只有promise对象才会有then()方法。并且,下一个then能获取到上一个then的返回值。

// ...

then(resolveFun, rejectFun) {
  // 返回全新promise
  return new MyPromise((resolve, reject) => {
    // 定义新方法
    const _resolveFun = (val) => {
      // 执行老方法获取结果,以便区分返回的是普通值还是promise
      const result = resolveFun(val);

      result instanceof MyPromise
        ? result.then(resolve, reject)
        : resolve(result);
    };

    const _rejectFun = (val) => {
      // 执行老方法获取结果,以便区分返回的是普通值还是promise
      const result = rejectFun(val);

      result instanceof MyPromise
        ? result.then(resolve, reject)
        : resolve(result);
    };

    // 将回调添加进各自队列
    this.resolveQueue.push(_resolveFun);
    this.rejectQueue.push(_rejectFun);
  });
}

这里的重点是我们返回了一个新的MyPromise对象,并且新写了resolvereject方法,然后加入各自的队列。

对于新的resolvereject方法,我们先执行老方法获取结果,然后针对结果的类型进行不同的处理。对于回调返回普通值,我们只需要resolve就可以了(因为执行resolve实际上就相当于在执行then里面的resolve回调函数)。如果是promise我们就调用.thenresolvereject传递过去(传递过去后,在外部调用resolvereject实际上就相当于在调用里面的resolvereject)。

这里是比较难理解的。需要反复多看几次。😇

好啦我们来测试下链式调用

const mp1 = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve("success");
  });
});

mp1
  .then((res) => {
    console.log(res);
    return "success2";
  })
  .then((res) => {
    console.log(res);
    return new MyPromise((resolve, reject) => {
      setTimeout(() => {
        resolve("success3");
      });
    });
  })
  .then((res) => {
    console.log(res);
  });

image.png

依次输出了success success2 success3,链式调用也实现了。

到这里我们的promise就完美了吗?答案是否定的。它还存在很多问题。

问题优化

比如 值穿透。

值穿透就是我们可以一直.then(),结果会一直传递。

const mp1 = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve("success");
  });
});

mp1.then().then().then((res) => {
  console.log(res);
});

直接报错,很明显我们这里是不支持值穿透的。

image.png

值穿透

要改这个问题也很简单,就是then方法中,在将回调添加到回调数组之前进行判断。如果没有传递方法我们需要手动给他添加一个方法。

then(resolveFun, rejectFun) {
  // 根据规范,如果then的参数不是function,我们需要手动创建相应函数,让链式调用继续往下执行
  typeof resolveFun !== "function"
    ? (resolveFun = (value) => value)
    : null;
  typeof rejectFun !== "function"
    ? (rejectFun = (reason) => {
        throw new Error(
          reason instanceof Error ? reason.message : reason
        );
      })
    : null;

  return new MyPromise((resolve, reject) => {
    // ...
  });
}

对于resolve,如果没有传递回调,我们就创建一个返回自身值的函数((value) => value)当做回调。否则不做处理。

对于reject,如果没有传递回调,我们就创建一个返回错误的函数((reason) => throw new Error())当做回调。否则不做处理。

我们再来测试一下

const mp1 = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve("success");
  });
});

mp1.then().then().then((res) => {
  console.log(res);
});

输出success

image.png

值穿透问题完美解决。

同步任务

细心的同学可能发现,我们每次在new MyPromise里面在执行resolvereject的时候都需要写在setTimeout里面。

const mp1 = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve("success");
  });
});

那不写到setTimeout里面可不可以呢?目前肯定是不可以的。

const mp1 = new MyPromise((resolve, reject) => {
  resolve("success");
});

mp1.then((res) => {
  console.log(res);
});

我们来测一下,返现什么也没输出

image.png

原因就是我们在执行resolve/reject的时候回调数组里面还没回调。所以后面再通过then方法添加回调也就没有意义了。

那怎么解决这个问题呢?

其实只要我们的resolve/reject方法异步执行就可以了。这样就会先执行then收集所有回调函数,后面resolve/reject的时候再触发回调。

我们知道,异步有宏任务和微任务之分,由于原生Promisethen是微任务,所以我们这里也使用微任务。我们这里使用MutationObserver来模拟微任务。

// ...

_runMicroTask(callback) {
  // 使用浏览器MutationObserver WEB.API实现then方法的微任务机制
  let count = 0;
  const observer = new MutationObserver(callback);
  // 创建文本节点,节约资源
  const textNode = document.createTextNode(String(count));
  observer.observe(textNode, {
    // 当文本改变时触发回调
    characterData: true,
  });
  // 改变文本,回调callback触发
  textNode.data = String(++count);
}

constructor(executor) {
  // 实现resolve
  const resolve = (val) => {
    const run = () => {
      // 对应规范中的"状态只能由pending到fulfilled或rejected"
      if (this.status !== PENDING) {
        return;
      }
      // 变更状态
      this.status = FULFILLED;
      while (this.resolveQueue.length) {
        const callback = this.resolveQueue.shift();
        callback(val);
      }
    };
    // 变成微任务执行
    this._runMicroTask(run);
  };

  // 实现reject
  const reject = (val) => {
    const run = () => {
      // 对应规范中的"状态只能由pending到fulfilled或rejected"
      if (this.status !== PENDING) {
        return;
      }
      // 变更状态
      this.status = REJECTED;
      while (this.rejectQueue.length) {
        const callback = this.rejectQueue.shift();
        callback(val);
      }
    };
    // 变成微任务执行
    this._runMicroTask(run);
  };

  // new Promise()时立即执行executor,并传入resolve和reject
  executor(resolve, reject);
}

// ...

我们再来测试一下

const mp1 = new MyPromise((resolve, reject) => {
  resolve("success");
});

mp1.then((res) => {
  console.log(res);
});

诶,输出success。这样对于同步的resolve/reject也能正确输出啦。

image.png

状态已变更

下面我们再来看个例子。

const mp1 = new MyPromise((resolve, reject) => {
  resolve("success");
});

mp1.then((res) => {
  console.log(res);
});

setTimeout(() => {
  mp1.then((res) => {
    console.log(res);
  });
});

上面会输出几个success呢?诶,这个不是上面说过了的吗。多次调用多次执行,会输出两个success。答案真的是两次吗?

我们运行测试一下

image.png

啊,只输出了一次。这是怎么回事呢?其实就是在resolve("success")执行之前只收集到了第一个then的回调,执行完后变成fulfilled的了,后面再从setTimeout里面收集到回调就不会再执行了。

那要怎么解决这个问题呢?那还得从我们的then方法做文章了。在then方法里面我们得判断,如果当前状态不是pending,我们的回调应该要立即执行。并且还要将上一次执行的结果传递过去。

// ...

// 定义变量存储最近一次promise返回的值
value = null;

// ...

constructor(executor) {
  // 实现resolve
  const resolve = (val) => {
    const run = () => {
      // ...
      // 变更状态
      this.status = FULFILLED;
      // 存储最近一次执行的值
      this.value = val;
      while (this.resolveQueue.length) {
        const callback = this.resolveQueue.shift();
        callback(val);
      }
    };
    this._runMicroTask(run);
  };

  // 实现reject
  const reject = (val) => {
    const run = () => {
      // ...
      // 变更状态
      this.status = REJECTED;
      // 存储最近一次执行的值
      this.value = val;
      while (this.rejectQueue.length) {
        const callback = this.rejectQueue.shift();
        callback(val);
      }
    };
    this._runMicroTask(run);
  };

  // new Promise()时立即执行executor,并传入resolve和reject
  executor(resolve, reject);
}

then(resolveFun, rejectFun) {
  // ...
  return new MyPromise((resolve, reject) => {
    // ...

    // 状态判断,不是PENDING 立即执行回调
    switch (this.status) {
      case PENDING:
        // 将回调添加进各自队列
        this.resolveQueue.push(_resolveFun);
        this.rejectQueue.push(_rejectFun);
        break;
      case FULFILLED:
        _resolveFun(this.value);
        break;
      case REJECTED:
        _rejectFun(this.value);
        break;
    }
  });
}

// ...

好,我们再来测试

const mp1 = new MyPromise((resolve, reject) => {
  resolve("success");
});

mp1.then((res) => {
  console.log(res);
});

setTimeout(() => {
  mp1.then((res) => {
    console.log(res);
  });
});

输出两个success,对于状态变更后续添加的回调我们也能解决。

image.png

错误处理

下面我们再来看一个例子

const mp1 = new MyPromise((resolve, reject) => {
  throw new Error("出错啦");
});

mp1.then(
  (res) => {
    console.log(res);
  },
  (err) => {
    console.log(err);
  }
);

我们发现,抛出的错误并没有按理想进入我们的then方法的第二个回调。而是直接报错。

image.png

对于 new MyPromise()里面出现的不可预测的错误的处理,其实只要在我们构造函数执行executor的时候处理就好了。

constructor(executor) {
  // ...
  try {
    executor(resolve, reject);
  } catch (error) {
    reject(error);
  }
}

我们再来测试一下,发现错误已经被我们处理啦。不再是 uncaught Error 啦。

image.png

对于then里面抛出的错误我们还没处理

const mp1 = new MyPromise((resolve, reject) => {
  resolve("success");
});

mp1
  .then(
    (res) => {
      console.log(res);
      throw new Error("出错啦");
    },
    (err) => {
      console.log(err);
    }
  )
  .then(
    (res) => {
      console.log(res);
    },
    (err) => {
      console.log(err);
    }
  );

image.png

我们再来处理下then里面的错误。then里面抛出的错误,我们需要在执行原始回调的时候处理就可以了。

then(resolveFun, rejectFun) {
  // ...

  return new MyPromise((resolve, reject) => {
    // 定义新方法
    const _resolveFun = (val) => {
      // 添加 try catch 处理错误
      try {
        // 执行老方法获取结果,以便区分返回的是普通值还是promise
        const result = resolveFun(val);

        result instanceof MyPromise
          ? result.then(resolve, reject)
          : resolve(result);
      } catch (error) {
        reject(error);
      }
    };

    const _rejectFun = (val) => {
      // 添加 try catch 处理错误
      try {
        // 执行老方法获取结果,以便区分返回的是普通值还是promise
        const result = rejectFun(val);

        result instanceof MyPromise
          ? result.then(resolve, reject)
          : resolve(result);
      } catch (error) {
        reject(error);
      }
    };

    // ...
  });
}

我们再来测试一下,发现错误已经被我们处理啦。不再是 uncaught Error 啦。

image.png

初步完整代码

好啦,现在我们的promise基本上就已经符合Promise A+规范啦。

// 定义Promise/A+规范的三种状态
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";

class MyPromise {
  // 定义两个数组,存储回调
  resolveQueue = [];
  rejectQueue = [];
  status = PENDING;
  value = null;

  // 模拟微任务执行回调
  _runMicroTask(callback) {
    // 使用浏览器MutationObserver WEB.API实现then方法的微任务机制
    let count = 0;
    const observer = new MutationObserver(callback);
    // 创建文本节点,节约资源
    const textNode = document.createTextNode(String(count));
    observer.observe(textNode, {
      // 当文本改变时触发回调
      characterData: true,
    });
    // 改变文本,回调callback触发
    textNode.data = String(++count);
  }

  constructor(executor) {
    // 实现resolve
    const resolve = (val) => {
      const run = () => {
        // 对应规范中的"状态只能由pending到fulfilled或rejected"
        if (this.status !== PENDING) {
          return;
        }
        // 变更状态
        this.status = FULFILLED;
        // 存储最近一次执行的值
        this.value = val;
        while (this.resolveQueue.length) {
          const callback = this.resolveQueue.shift();
          callback(val);
        }
      };
      this._runMicroTask(run);
    };

    // 实现reject
    const reject = (val) => {
      const run = () => {
        // 对应规范中的"状态只能由pending到fulfilled或rejected"
        if (this.status !== PENDING) {
          return;
        }
        // 变更状态
        this.status = REJECTED;
        // 存储最近一次执行的值
        this.value = val;
        while (this.rejectQueue.length) {
          const callback = this.rejectQueue.shift();
          callback(val);
        }
      };
      this._runMicroTask(run);
    };

    // new Promise()时立即执行executor,并传入resolve和reject
    try {
      executor(resolve, reject);
    } catch (error) {
      reject(error);
    }
  }
  
  then(resolveFun, rejectFun) {
    // 根据规范,如果then的参数不是function,我们需要手动创建相应函数,让链式调用继续往下执行
    typeof resolveFun !== "function"
      ? (resolveFun = (value) => value)
      : null;
    typeof rejectFun !== "function"
      ? (rejectFun = (reason) => {
          throw new Error(
            reason instanceof Error ? reason.message : reason
          );
        })
      : null;

    return new MyPromise((resolve, reject) => {
      // 定义新方法
      const _resolveFun = (val) => {
        // 执行老方法获取结果,以便区分返回的是普通值还是promise
        try {
          const result = resolveFun(val);

          result instanceof MyPromise
            ? result.then(resolve, reject)
            : resolve(result);
        } catch (error) {
          reject(error);
        }
      };

      const _rejectFun = (val) => {
        // 执行老方法获取结果,以便区分返回的是普通值还是promise
        try {
          const result = rejectFun(val);

          result instanceof MyPromise
            ? result.then(resolve, reject)
            : resolve(result);
        } catch (error) {
          reject(error);
        }
      };
      
      // 判断状态,解决状态已变更问题
      switch (this.status) {
        case PENDING:
          // 将回调添加进各自队列
          this.resolveQueue.push(_resolveFun);
          this.rejectQueue.push(_rejectFun);
          break;
        case FULFILLED:
          _resolveFun(this.value);
          break;
        case REJECTED:
          _rejectFun(this.value);
          break;
      }
    });
  }
}

我们知道,promise其实还是提供了一些其它方法,下面我们也来实现一下。

实现其它方法

Promise.resolve()

Promise.resolve(value)返回一个 promise 并且是 fulfilled 状态的对象
static resolve(value) {
  // 根据规范, 如果参数是Promise实例, 直接return这个实例
  // 否则返回一个新的promise并且是fulfilled状态
  if(value instanceof MyPromise) return value 
  return new MyPromise(resolve => resolve(value))
}

Promise.reject()

Promise.reject()方法返回一个 promise 并且是 reject 状态的对象
static reject(reason) {
  return new MyPromise((resolve, reject) => reject(reason))
}

Promise.prototype.catch()

catch()方法返回一个Promise,并且处理拒绝的情况。它的行为与调用Promise.prototype.then(undefined, onRejected) 相同。
// catch方法其实就是执行一下then的第二个回调
catch(rejectFn) {
  return this.then(undefined, rejectFn)
}

我们来测试一下

const mp1 = new MyPromise((resolve, reject) => {
  resolve("success");
});

mp1
  .then(
    (res) => {
      console.log(res);
      throw new Error("出错啦");
    }
  )
  .catch((err) => {
    console.log("catch err", err);
  });

错误正常捕获

image.png

Promise.prototype.finally()

finally()方法接受一个回调函数,但是该回调函数没有参数。在promise结束时,无论结果是fulfilled或者是rejected,都会执行指定的回调函数。并且返回一个Promise,也就是在finally之后,还可以继续then。并且会将值原封不动的传递给后面的then。
finally(callback) {
  return this.then(
    (res) => {
      callback();
      return MyPromise.resolve(res);
    },
    (err) => {
      callback();
      return MyPromise.reject(err);
    }
  );
}

我们来测试一下

const mp1 = new MyPromise((resolve, reject) => {
  resolve("success");
});

mp1
  .then(
    (res) => {
      console.log(res);
      throw new Error("出错啦");
    },
    (err) => {
      console.log(err);
    }
  )
  .catch((err) => {
    console.log("catch err", err);
    return 123;
  })
  .finally(() => {
    console.log("不管成功失败我都会执行");
  })
  .then((res) => {
    console.log(res);
  });

finally总会执行,并且返回的是promise,并且会将值原封不动的传递给后面的then

image.png

Promise.all()

Promise.all(iterable) 参数为一组 Promise 实例组成的数组,用于将多个 Promise实例包装成一个新的 Promise实例。

当数组中的Promise实例都为都 Resolved 的时候,Promise.all() 的状态才会 Resolved,否则为Rejected。并且Rejected是第一个被RejectedPromise 的返回值。

static all(promiseArr) {
  return new MyPromise((resolve, reject) => {
    const length = promiseArr.length;
    let count = 0;
    let values = [];

    for (let i = 0; i < length; i++) {
      const p = promiseArr[i];
      // MyPromise.resolve()处理传入值不为Promise的情况
      MyPromise.resolve(p).then(
        (res) => {
          values[i] = res;
          count++;
          if (count === length) {
            resolve(values);
          }
        },
        (err) => {
          // 只要子元素promise中有任何一个reject,则返回的promise rejected
          reject(err);
        }
      );
    }
  });
}

我们来测试一下

const mp2 = MyPromise.resolve("all success");
const mp3 = MyPromise.resolve("all success2");

MyPromise.all([mp2, mp3]).then(
  (res) => {
    console.log(res);
  },
  (err) => {
    console.log(err);
  }
);

执行正确,返回所有成功结果

image.png

Promise.race()

race方法与 all方法不同, race方法中只要对象中有一个状态改变了,它的状态就跟着改变,并将那个改变状态实例的返回值传递给回调函数。

也就是不管失败与成功第一个Promise的返回值就是race方法的返回值。resolve就会进入then方法,否则进入catch方法

static race(promiseArr) {
  return new MyPromise((resolve, reject) => {
    for (let p of promiseArr) {
      // MyPromise.resolve()处理传入值不为Promise的情况
      MyPromise.resolve(p).then(
        (res) => {
          resolve(res);
        },
        (err) => {
          reject(err);
        }
      );
    }
  });
}

来测试一下

const mp4 = MyPromise.resolve("race success");
const mp5 = MyPromise.reject("race error");

MyPromise.race([mp4, mp5]).then(
  (res) => {
    console.log(res);
  },
  (err) => {
    console.log(err);
  }
);

执行正确,返回第一个promise的结果

image.png

完整代码

// 定义Promise/A+规范的三种状态
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";

class MyPromise {
  // 定义两个数组,存储回调
  resolveQueue = [];
  rejectQueue = [];
  // 状态
  status = PENDING;
  // 值
  value = null;

  // 模拟微任务执行回调
  _runMicroTask(callback) {
    // 使用浏览器MutationObserver WEB.API实现then方法的微任务机制
    let count = 0;
    const observer = new MutationObserver(callback);
    // 创建文本节点,节约资源
    const textNode = document.createTextNode(String(count));
    observer.observe(textNode, {
      // 当文本改变时触发回调
      characterData: true,
    });
    // 改变文本,回调callback触发
    textNode.data = String(++count);
  }

  constructor(executor) {
    // 实现resolve
    const resolve = (val) => {
      const run = () => {
        // 对应规范中的"状态只能由pending到fulfilled或rejected"
        if (this.status !== PENDING) {
          return;
        }
        // 变更状态
        this.status = FULFILLED;
        // 存储最近一次执行的值
        this.value = val;
        while (this.resolveQueue.length) {
          const callback = this.resolveQueue.shift();
          callback(val);
        }
      };
      this._runMicroTask(run);
    };

    // 实现reject
    const reject = (val) => {
      const run = () => {
        // 对应规范中的"状态只能由pending到fulfilled或rejected"
        if (this.status !== PENDING) {
          return;
        }
        // 变更状态
        this.status = REJECTED;
        // 存储最近一次执行的值
        this.value = val;
        while (this.rejectQueue.length) {
          const callback = this.rejectQueue.shift();
          callback(val);
        }
      };
      this._runMicroTask(run);
    };

    // new Promise()时立即执行executor,并传入resolve和reject
    try {
      executor(resolve, reject);
    } catch (error) {
      reject(error);
    }
  }

  then(resolveFun, rejectFun) {
    // 根据规范,如果then的参数不是function,我们需要手动创建相应函数,让链式调用继续往下执行
    typeof resolveFun !== "function"
      ? (resolveFun = (value) => value)
      : null;
    typeof rejectFun !== "function"
      ? (rejectFun = (reason) => {
          throw new Error(
            reason instanceof Error ? reason.message : reason
          );
        })
      : null;

    return new MyPromise((resolve, reject) => {
      // 定义新方法
      const _resolveFun = (val) => {
        // 执行老方法获取结果,以便区分返回的是普通值还是promise
        try {
          const result = resolveFun(val);

          result instanceof MyPromise
            ? result.then(resolve, reject)
            : resolve(result);
        } catch (error) {
          reject(error);
        }
      };

      const _rejectFun = (val) => {
        // 执行老方法获取结果,以便区分返回的是普通值还是promise
        try {
          const result = rejectFun(val);

          result instanceof MyPromise
            ? result.then(resolve, reject)
            : resolve(result);
        } catch (error) {
          reject(error);
        }
      };

      switch (this.status) {
        case PENDING:
          // 将回调添加进各自队列
          this.resolveQueue.push(_resolveFun);
          this.rejectQueue.push(_rejectFun);
          break;
        case FULFILLED:
          _resolveFun(this.value);
          break;
        case REJECTED:
          _rejectFun(this.value);
          break;
      }
    });
  }

  //静态的resolve方法
  static resolve(value) {
    if (value instanceof MyPromise) return value; // 根据规范, 如果参数是Promise实例, 直接return这个实例
    return new MyPromise((resolve) => resolve(value));
  }

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

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

  finally(callback) {
    return this.then(
      (res) => {
        callback();
        return MyPromise.resolve(res);
      },
      (err) => {
        callback();
        return MyPromise.reject(err);
      }
    );
  }

  static all(promiseArr) {
    return new MyPromise((resolve, reject) => {
      const length = promiseArr.length;
      let count = 0;
      let values = [];

      for (let i = 0; i < length; i++) {
        const p = promiseArr[i];
        // MyPromise.resolve()处理传入值不为Promise的情况
        MyPromise.resolve(p).then(
          (res) => {
            values[i] = res;
            count++;
            if (count === length) {
              resolve(values);
            }
          },
          (err) => {
            // 只要子元素promise中有任何一个reject,则返回的promise rejected
            reject(err);
          }
        );
      }
    });
  }

  // 添加静态race方法
  static race(promiseArr) {
    return new MyPromise((resolve, reject) => {
      for (let p of promiseArr) {
        // MyPromise.resolve()处理传入值不为Promise的情况
        MyPromise.resolve(p).then(
          (res) => {
            resolve(res);
          },
          (err) => {
            reject(err);
          }
        );
      }
    });
  }
}

好啦,关于手写promise就讲到这里啦。小伙伴们是否学会了呢?🤭

扩展

既然分析到了promise,我们干脆再来分析下async、await

async await

我们知道,asyncawait是既能达到暂停执行又能达到异步执行。那它到底是怎么实现这种效果的呢?

其实是promisegenerator 的语法糖。对generator不熟悉的可以先看看笔者写的js异步编程。因为promise它是异步执行的,generator又能实现暂停执行。它们组合刚好能达到asyncawait这样的效果。

下面我们来看个asyncawait的例子

const p1 = Promise.resolve("success1");
const p2 = Promise.resolve("success2");
const p3 = Promise.resolve("success3");

const test = async () => {
  const result3 = await p3;
  console.log(result3);
  const result1 = await p1;
  console.log(result1);
  const result2 = await p2;
  console.log(result2);
};

console.log("同步代码执行啦");

test(); // 同步代码执行啦 success3 success1 success2

asyncawait中的代码是异步并且是按顺序执行的。

image.png

下面我们使用promisegenerator来改造一下

const p1 = Promise.resolve("success1");
const p2 = Promise.resolve("success2");
const p3 = Promise.resolve("success3");

function* myGenerator() {
  yield p3;
  yield p1;
  yield p2;
}

// 手动执行迭代器
const gen = myGenerator();
gen.next().value.then((res) => {
  console.log(res);
  gen.next().value.then((res) => {
    console.log(res);
    gen.next().value.then((res) => {
      console.log(res);
    });
  });
});

诶,确实也能达到同样的效果,但是需要自己手动调用next一步一步执行,并且每次异步执行的结果在generator函数也获取不到。

image.png

我们知道generator是能传递参数的,所以我们再来改造一下

const p1 = Promise.resolve("success1");
const p2 = Promise.resolve("success2");
const p3 = Promise.resolve("success3");

function* myGenerator() {
  const result3 = yield p3;
  console.log(result3);
  const result1 = yield p1;
  console.log(result1);
  const result2 = yield p2;
  console.log(result2);
}

// 手动执行迭代器
const gen = myGenerator();
gen.next().value.then((res3) => {
  gen.next(res3).value.then((res1) => {
    gen.next(res1).value.then((res2) => {
      gen.next(res2);
    });
  });
});

诶,也能正常输出,并且能在generator函数获取到结果啦。但是呢还需要手动执行next。能不能自动执行呢?

image.png

下面我们来封装一个自动执行函数,让它自动执行

const p1 = Promise.resolve("success1");
const p2 = Promise.resolve("success2");
const p3 = Promise.resolve("success3");

const _run = (gen) => {
  // 获取迭代器
  const it = gen();
  // 封装递归方法
  const _next = (val) => {
    const result = it.next(val);
    if (result.done) return result.value;
    result.value.then((res) => {
      _next(res);
    });
  };

  // 第一次调用
  _next();
};

_run(myGenerator);

测试一下,也能正常输出。

image.png

但是现在的async和await还是有些问题的。

  1. 需要兼容基本类型:这段代码能自动执行的前提是yield后面跟Promise,为了兼容后面跟着基本类型值的情况,我们需要把yield跟的内容(gen().next.value)都用Promise.resolve()转化一遍
  2. 缺少错误处理:上边代码里的Promise如果执行失败,或者里面同步代码抛错,就会导致后续执行直接中断,我们需要通过调用Generator.prototype.throw()reject(error),把错误抛出来,才能被外层的try-catch捕获到
  3. 返回值是Promiseasync/await的返回值是一个Promise,我们这里也需要保持一致,方法返回一个Promise就可以啦。

问题优化

我们新写一个自动执行函数,来将这些问题解决。

const _run2 = (gen) => {
  return new Promise((resolve, reject) => {
    // 获取迭代器
    const it = gen(); // 封装递归方法

    const _next = (val) => {
      let result = null;
      try {
        result = it.next(val);
      } catch (error) {
        reject(error);
      }
      if (result.done) return resolve(result.value);
      Promise.resolve(result.value).then(
        (res) => {
          _next(res);
        },
        (err) => {
          it.throw(err);
        }
      );
    };

    // 第一次调用
    _next();
  });
};

我们来测试一下

const p4 = Promise.resolve("success4");
const p5 = Promise.resolve("success5");
const p6 = Promise.reject("error6");

function* myGenerator2() {
  try {
    const result4 = yield p4;
    console.log(result4);
    const result5 = yield p5;
    console.log(result5);
    const result6 = yield p6;
    console.log(result6);
  } catch (error) {
    console.log("异常被捕获啦", error);
  }
}

_run2(myGenerator2);

异常被成功捕获了。

image.png

到这对于asyncawait原理也讲得差不多了。核心就是使用promisegenerator,并实现generator自动执行的方法。

后记

感谢小伙伴们的耐心观看,本文为笔者个人学习笔记,如有谬误,还请告知,万分感谢!如果本文对你有所帮助,还请点个关注点个赞~,您的支持是笔者不断更新的动力!

相关文章
|
4月前
|
消息中间件 存储 前端开发
「3.4w字」超保姆级教程带你实现Promise的核心功能
该文章通过详细的步骤和示例代码,逐步介绍了如何从零开始实现一个符合ECMAScript标准的Promise对象,涵盖了Promise的基本使用、状态管理、链式调用、错误处理机制及Promise.all和Promise.resolve等方法的实现。
「3.4w字」超保姆级教程带你实现Promise的核心功能
|
存储 移动开发 JavaScript
带你读《现代Javascript高级教程》二十六、JS中的异步编程与Promise(1)
带你读《现代Javascript高级教程》二十六、JS中的异步编程与Promise(1)
|
前端开发 JavaScript
带你读《现代Javascript高级教程》二十六、JS中的异步编程与Promise(2)
带你读《现代Javascript高级教程》二十六、JS中的异步编程与Promise(2)
|
存储 前端开发 JavaScript
带你读《现代Javascript高级教程》二十七、实现符合Promise/A+规范的Promise(1)
带你读《现代Javascript高级教程》二十七、实现符合Promise/A+规范的Promise(1)
|
前端开发 JavaScript
带你读《现代Javascript高级教程》二十七、实现符合Promise/A+规范的Promise(2)
带你读《现代Javascript高级教程》二十七、实现符合Promise/A+规范的Promise(2)
|
前端开发 JavaScript
带你读《现代Javascript高级教程》二十七、实现符合Promise/A+规范的Promise(3)
带你读《现代Javascript高级教程》二十七、实现符合Promise/A+规范的Promise(3)
|
资源调度 前端开发 JavaScript
带你读《现代Javascript高级教程》二十七、实现符合Promise/A+规范的Promise(4)
带你读《现代Javascript高级教程》二十七、实现符合Promise/A+规范的Promise(4)
|
前端开发 JavaScript 测试技术
带你读《现代Javascript高级教程》二十七、实现符合Promise/A+规范的Promise(5)
带你读《现代Javascript高级教程》二十七、实现符合Promise/A+规范的Promise(5)
|
前端开发 JavaScript
带你读《现代Javascript高级教程》二十七、实现符合Promise/A+规范的Promise(6)
带你读《现代Javascript高级教程》二十七、实现符合Promise/A+规范的Promise(6)
|
存储 移动开发 前端开发
《现代Javascript高级教程》JavaScript中的异步编程与Promise
JS中的异步编程与Promise 一、JavaScript的异步编步机制 在了解JavaScript的异步机制之前,我们首先需要理解JavaScript是一种单线程语言。单线程就意味着所有的任务需要按照顺序一次执行,如果前一个任务没有完成,后一个任务就无法开始。这个特性在执行大量或耗时任务时可能会导致阻塞或者界面卡死,这显然是不可取的。
97 1