原生JavaScript实现Promise

352 阅读8分钟

技术要点

  1. 链式方法调用
  2. 状态机制管理
  3. 原型方法调用
  4. 任务队列调度

代码实现

话不多说,上手开写。

最简单的peomise

使用Promise时必须new一个promise对象,通过then执行成功的回调

new Promise(function (resolve, reject) {
  resolve(1)
}).then(res => {
  console.log('成功' + res);
}, err => {
  console.log('失败' + err);
});

故promise是一个构造函数且包含then的原型方法。

function PromiseA(callback) {
  // 1. 参数校验
  if (!callback) throw new Error('callback is required!');
  var self = this;
  
  // 2. 定义状态 pendding, resolved, rejected
  this.status = 'pendding';
  
  // 4.2 成功或失败数据需要存储
  this.resolveData = undefined;
  this.rejectData = undefined;
  
  // 4.1 callback需要两个参数,此处定义
  function resolve(data) {
    self.resolveData = data;
    self.status = 'resolved';
  }
  function reject(data) {
    self.rejectData = data;
    self.status = 'rejected';
  }
  
  // 3. 执行promise,注意同步代码可能会报错,需主动捕获错误
  try {
    callback(resolve, reject)
  } catch(err) {
    reject(err)
  }
}

// 5. 原型挂载then方法
PromiseA.prototype.then = function (resolve, reject) {
  if (this.status === 'resolved') {
    resolve(this.resolveData)
  } else {
    reject(this.rejectData)
  }
}

// 测试用例1
new PromiseA(function (resolve, reject) {
  reject(1)
}).then(res => {
  console.log('成功' + res);
}, err => {
  console.log('失败' + err);
});

上述实现了简单的Promise,但是遇到包裹setTimeout时就失效了。

补充逻辑,支持setTimeout

由于setTimeout是宏任务,需等待执行完成以后在执行then的回调,因此要添加执行队列,待执行完成以后在进行then的调用

function PromiseA(callback) {
  // 1. 参数校验
  if (!callback) throw new Error('callback is required!');
  var self = this;
  // 2. 定义状态 pendding, resolved, rejected
  this.status = 'pendding';

  // 4.2 成功或失败数据需要存储
  this.resolveData = undefined;
  this.rejectData = undefined;

  // 6.2 补充队列字段
  this.queues = [];

  // 4.1 callback需要两个参数,此处定义
  function resolve(data) {
    self.resolveData = data;
    self.status = 'resolved';
    // 6.3 补充队列逻辑
    if (self.queues.length) {
      self.queues.forEach(fn => fn());
    }
  }
  function reject(data) {
    self.rejectData = data;
    self.status = 'rejected';
    // 6.4 补充队列逻辑
    if (self.queues.length) {
      self.queues.forEach(fn => fn());
    }
  }

  // 3. 执行promise,注意同步代码可能会报错,需主动捕获错误
  try {
    callback(resolve, reject)
  } catch(err) {
    reject(err)
  }
}

// 5. 原型挂载then方法
PromiseA.prototype.then = function (resolve, reject) {
  const self = this;
  // 6. 添加pendding状态判断
  if (this.status === 'pendding') {
    this.queues.push(function () {
      if (self.status === 'resolved') {
        resolve(self.resolveData)
      } else {
        reject(self.rejectData)
      }
    })
  } else if (this.status === 'resolved') {
    resolve(this.resolveData)
  } else {
    reject(this.rejectData)
  }
}

// 测试用例1
new PromiseA(function (resolve, reject) {
  reject(1)
}).then(res => {
  console.log('成功' + res);
}, err => {
  console.log('失败' + err);
});

// 测试用例2
new PromiseA(function (resolve, reject) {
  setTimeout(function (){
    reject(2)
  }, 1000)
}).then(res => {
  console.log('成功' + res);
}, err => {
  console.log('失败' + err);
});

代码通篇拷贝,只需关注测试用例2步骤6,历史测试用例保留原因为修改PromiseA需保证没有影响到历史代码。

在实际使用中,不仅有then还会有catch,经常会通过catch处理异常。

补充catch,完善逻辑

catch在Promise使用中通过链式调用相应,说明catch为原型方法,重点在于如何跳过then的链调用catch的链

function PromiseA(callback) {
  // 1. 参数校验
  if (!callback) throw new Error('callback is required!');
  var self = this;
  // 2. 定义状态 pendding, resolved, rejected
  this.status = 'pendding';

  // 4.2 成功或失败数据需要存储
  this.resolveData = undefined;
  this.rejectData = undefined;

  // 6.2 补充队列字段
  this.queues = [];

  // 4.1 callback需要两个参数,此处定义
  function resolve(data) {
    self.resolveData = data;
    self.status = 'resolved';
    // 6.3 补充队列逻辑
    if (self.queues.length) {
      self.queues.forEach(fn => fn());
    }
  }
  function reject(data) {
    self.rejectData = data;
    self.status = 'rejected';
    // 6.4 补充队列逻辑
    if (self.queues.length) {
      self.queues.forEach(fn => fn());
    }
  }

  // 3. 执行promise,注意同步代码可能会报错,需主动捕获错误
  try {
    callback(resolve, reject)
  } catch(err) {
    reject(err)
  }
}

// 5. 原型挂载then方法
PromiseA.prototype.then = function (resolve, reject) {
  const self = this;
  // 6. 添加pendding状态判断
  if (this.status === 'pendding') {
    this.queues.push(function () {
      if (self.status === 'resolved') {
        resolve(self.resolveData)
      } else {
        reject(self.rejectData)
      }
    })
  } else if (this.status === 'resolved') {
    resolve(this.resolveData)
  } else if (this.status === 'rejected' && reject) {
    // 7.1 完善判断
    reject(this.rejectData)
  } else {
    // 7.2 链调用需要导出自身
    return this;
  }
}

// 7.3 补充catch原型方法
PromiseA.prototype.catch = function (reject) {
  reject(this.rejectData)
}

// 测试用例1
new PromiseA(function (resolve, reject) {
  reject(1)
}).then(res => {
  console.log('成功' + res);
}, err => {
  console.log('失败' + err);
});

// 测试用例2
new PromiseA(function (resolve, reject) {
  setTimeout(function (){
    reject(2)
  }, 1000)
}).then(res => {
  console.log('成功' + res);
}, err => {
  console.log('失败' + err);
});

// 测试用例3
new PromiseA(function (resolve, reject) {
  reject(3)
}).then(res => {
  console.log('成功' + res);
}).catch(err => {
  console.log('失败' + err);
});

代码通篇拷贝,只需关注测试用例3步骤7,历史测试用例保留原因为修改PromiseA需保证没有影响到历史代码。

测试案例3成功执行,导出this解决链的问题,且历史不受影响。

完善catch,修复链调用

但是加入setTimeout后出现报错,需解决setTimeout任务后调用then或catch

function PromiseA(callback) {
  // 1. 参数校验
  if (!callback) throw new Error('callback is required!');
  var self = this;
  // 2. 定义状态 pendding, resolved, rejected
  this.status = 'pendding';

  // 4.2 成功或失败数据需要存储
  this.resolveData = undefined;
  this.rejectData = undefined;

  // 6.2 补充队列字段
  this.queues = [];

  // 8.4 补充异常队列
  this.rejectedQueues = [];

  // 4.1 callback需要两个参数,此处定义
  function resolve(data) {
    self.resolveData = data;
    self.status = 'resolved';
    // 6.3 补充队列逻辑
    if (self.queues.length) {
      self.queues.forEach(fn => fn());
    }
  }
  function reject(data) {
    self.rejectData = data;
    self.status = 'rejected';
    // 6.4 补充队列逻辑
    if (self.queues.length) {
      self.queues.forEach(fn => fn());
    }
    // 8.5 补充异常队列逻辑
    if (self.rejectedQueues.length) {
      self.rejectedQueues.forEach(fn => fn());
    } else if (self.queues) {
      for (var i = 0; i < self.queues.length; i++) {
        var func = self.queues[i].bind(self);
        func();
      }
    }
  }

  // 3. 执行promise,注意同步代码可能会报错,需主动捕获错误
  try {
    callback(resolve, reject)
  } catch(err) {
    reject(err)
  }
}

// 5. 原型挂载then方法
PromiseA.prototype.then = function (resolve, reject) {
  const self = this;
  // 6. 添加pendding状态判断
  if (this.status === 'pendding') {
    this.queues.push(function () {
      if (self.status === 'resolved') {
        resolve(self.resolveData)
      } else if (self.status === 'rejected' && reject) {
        // 8.1 完善队列判断
        reject(self.rejectData)
      }
    })
    return this;
  } else if (this.status === 'resolved') {
    resolve(this.resolveData)
  } else if (this.status === 'rejected' && reject) {
    // 7.1 完善判断
    reject(this.rejectData)
  } else {
    // 7.2 链调用需要导出自身
    return this;
  }
}

// 7.3 补充catch原型方法
PromiseA.prototype.catch = function (reject) {
  const self = this;
  // 8.2 完善异常判断
  if (this.status === 'pendding') {
    // 8.3 补充异常队列
    this.rejectedQueues.push(function () {
      if (self.status === 'rejected' && reject) {
        reject(self.rejectData)
      }
    })
  } else if (this.status === 'rejected') {
    reject(this.rejectData)
  }
}

// 测试用例1
new PromiseA(function (resolve, reject) {
  reject(1)
}).then(res => {
  console.log('成功' + res);
}, err => {
  console.log('失败' + err);
});

// 测试用例2
new PromiseA(function (resolve, reject) {
  setTimeout(function (){
    reject(2)
  }, 1000)
}).then(res => {
  console.log('成功' + res);
}, err => {
  console.log('失败' + err);
});

// 测试用例3
new PromiseA(function (resolve, reject) {
  reject(3)
}).then(res => {
  console.log('成功' + res);
}).catch(err => {
  console.log('失败' + err);
});

// 测试用例4
new PromiseA(function (resolve, reject) {
  setTimeout(function (){
    reject(4)
  }, 1000)
}).then(res => {
  console.log('成功' + res);
}).catch(err => {
  console.log('失败' + err);
});

代码通篇拷贝,只需关注测试用例4步骤8,历史测试用例保留原因为修改PromiseA需保证没有影响到历史代码。

此时只实现了一层的链式调用,promise需要多层链式调用

多层链完善

继续补充多层链逻辑,兼容各种情况

function PromiseA(callback) {
  // 1. 参数校验
  if (!callback) throw new Error('callback is required!');
  var self = this;
  // 2. 定义状态 pendding, resolved, rejected
  this.status = 'pendding';

  // 4.2 成功或失败数据需要存储
  this.resolveData = undefined;
  this.rejectData = undefined;

  // 6.2 补充队列字段
  this.queues = [];

  // 8.4 补充异常队列
  this.rejectedQueues = [];

  // 4.1 callback需要两个参数,此处定义
  function resolve(data) {
    self.resolveData = data;
    self.status = 'resolved';
    // 6.3 补充队列逻辑
    if (self.queues.length) {
      self.queues.forEach(fn => fn());
    }
  }
  function reject(data) {
    self.rejectData = data;
    self.status = 'rejected';
    // 6.4 补充队列逻辑
    if (self.queues.length) {
      self.queues.forEach(fn => fn());
    }
    // 8.5 补充异常队列逻辑
    if (self.rejectedQueues.length) {
      self.rejectedQueues.forEach(fn => fn());
    }
  }

  // 3. 执行promise,注意同步代码可能会报错,需主动捕获错误
  try {
    callback(resolve, reject)
  } catch(err) {
    reject(err)
  }
}

// 5. 原型挂载then方法
PromiseA.prototype.then = function (resolve, reject) {
  const self = this;
  // 6. 添加pendding状态判断
  if (this.status === 'pendding') {
    this.queues.push(function () {
      if (self.status === 'resolved') {
        // 9.1 resolve会返回新的Promise,在此接收
        var result = resolve(self.resolveData)
        if (result) {
          // 9.2 判断resolve中promise的执行状态
          if (result.status === 'resolved') {
            // 9.3 若为成功,修改状态数据进入下一个链
            self.status = 'resolved';
            self.resolveData = result.resolveData;
          } else {
            // 9.4 若为异常,需检测是否有异常队列并执行异常队列的方法
            self.status = 'rejected';
            self.rejectData = result.rejectData;
            if (self.rejectedQueues.length) {
              self.rejectedQueues.forEach(fn => fn());
            }
          }
        }
      } else if (self.status === 'rejected' && reject) {
        // 8.1 完善队列判断
        reject(self.rejectData)
      }
    })
    return this;
  } else if (this.status === 'resolved') {
    resolve(this.resolveData)
  } else if (this.status === 'rejected' && reject) {
    // 7.1 完善判断
    reject(this.rejectData)
  } else {
    // 7.2 链调用需要导出自身
    return this;
  }
}

// 7.3 补充catch原型方法
PromiseA.prototype.catch = function (reject) {
  const self = this;
  // 8.2 完善异常判断
  if (this.status === 'pendding') {
    // 8.3 补充异常队列
    this.rejectedQueues.push(function () {
      if (self.status === 'rejected' && reject) {
        reject(self.rejectData)
      }
    })
  } else if (this.status === 'rejected') {
    reject(this.rejectData)
  }
}

// 测试用例1
new PromiseA(function (resolve, reject) {
  reject(1)
}).then(res => {
  console.log('成功' + res);
}, err => {
  console.log('失败' + err);
});

// 测试用例2
new PromiseA(function (resolve, reject) {
  setTimeout(function (){
    reject(2)
  }, 1000)
}).then(res => {
  console.log('成功' + res);
}, err => {
  console.log('失败' + err);
});

// 测试用例3
new PromiseA(function (resolve, reject) {
  reject(3)
}).then(res => {
  console.log('成功' + res);
}).catch(err => {
  console.log('失败' + err);
});

// 测试用例4
new PromiseA(function (resolve, reject) {
  setTimeout(function (){
    reject(4)
  }, 1000)
}).then(res => {
  console.log('成功' + res);
}).catch(err => {
  console.log('失败' + err);
});

// 测试用例5
new PromiseA(function (resolve, reject) {
  setTimeout(function (){
    resolve(5)
  }, 1000)
}).then(res => {
  console.log('成功' + res);
  return new PromiseA((resolve, reject) => {
    resolve(6)
  })
}).then(res => {
  console.log('成功' + res);
  return new PromiseA((resolve, reject) => {
    reject(7)
  })
}).then(res => {
  console.log('成功' + res);
}).catch(err => {
  console.log('失败' + err);
});

代码通篇拷贝,只需关注测试用例5步骤9,历史测试用例保留原因为修改PromiseA需保证没有影响到历史代码。

添加setTimeout,修复链遇到宏任务失效问题

遇到setTimeout后,链的调用会失效,输出混乱。上面那个例子完善了链,但因为是同步操作,所以没有表现出问题,加入setTimeout后问题出现。

主要原因是实例指向问题,解决此问题需要对this(或实例)指向有一定的逻辑性。

function PromiseA(callback) {
  // 1. 参数校验
  if (!callback) throw new Error('callback is required!');
  var self = this;
  // 2. 定义状态 pendding, resolved, rejected
  this.status = 'pendding';

  // 4.2 成功或失败数据需要存储
  this.resolveData = undefined;
  this.rejectData = undefined;

  // 6.2 补充队列字段
  this.queues = [];

  // 8.4 补充异常队列
  this.rejectedQueues = [];

  // 4.1 callback需要两个参数,此处定义
  function resolve(data) {
    self.resolveData = data;
    self.status = 'resolved';
    // 6.3 补充队列逻辑
    if (self.queues.length) {
      // 10.1 添加bind方法改变函数this指向,让this指向具体实例
      for (var i = 0; i < self.queues.length; i++) {
        var func = self.queues[i].bind(self);
        func();
      }
    }
  }
  function reject(data) {
    self.rejectData = data;
    self.status = 'rejected';
    // 8.5 补充异常队列逻辑
    if (self.rejectedQueues.length) {
      // 10.2 添加bind方法改变函数this指向,让this指向具体实例
      for (var i = 0; i < self.rejectedQueues.length; i++) {
        var func = self.rejectedQueues[i].bind(self);
        func();
      }
    } else if (self.queues) {
      for (var i = 0; i < self.queues.length; i++) {
        var func = self.queues[i].bind(self);
        func();
      }
    }
  }

  // 3. 执行promise,注意同步代码可能会报错,需主动捕获错误
  try {
    callback(resolve, reject)
  } catch(err) {
    reject(err)
  }
}

// 5. 原型挂载then方法
PromiseA.prototype.then = function (resolve, reject) {
  // 6. 添加pendding状态判断
  if (this.status === 'pendding') {
    this.queues.push(function () {
      // 10.3 删除原来定义的self,当前函数的this指向的就是实例,为了防止和外层混淆,定义新的self
      var self = this;
      if (self.status === 'resolved') {
        // 9.1 resolve会返回新的Promise,在此接收
        var result = resolve(self.resolveData)
        if (result) {
          // 10.4 resolve执行完成后,修改状态为pendding,防止后续then输出
          self.status = 'pendding';
          // 10.5 继承父级回调,删除父级回调,防止继续调用
          result.queues = self.queues.slice(1)
          self.queues.splice(1, self.queues.length)
          result.rejectedQueues = self.rejectedQueues
          // 9.2 判断resolve中promise的执行状态
          if (result.status === 'resolved') {
            // 9.3 若为成功,修改状态数据进入下一个链
            self.status = 'resolved';
            self.resolveData = result.resolveData;
          } else if (result.status === 'rejected') {
            // 9.4 若为异常,需检测是否有异常队列并执行异常队列的方法
            self.status = 'rejected';
            self.rejectData = result.rejectData;
            if (self.rejectedQueues.length) {
              self.rejectedQueues.forEach(fn => fn.bind(result)());
            }
          }
        }
      } else if (self.status === 'rejected' && reject) {
        // 8.1 完善队列判断
        reject(self.rejectData)
      }
      // 10.6 执行下一Promise的队列
      if (result && result.queues) {
        for (var i = 0; i < result.queues.length; i++) {
          var func = result.queues[i].bind(self);
          func();
        }
      }
    })
    return this;
  } else if (this.status === 'resolved') {
    resolve(this.resolveData)
  } else if (this.status === 'rejected' && reject) {
    // 7.1 完善判断
    reject(this.rejectData)
  } else {
    // 7.2 链调用需要导出自身
    return this;
  }
}

// 7.3 补充catch原型方法
PromiseA.prototype.catch = function (reject) {
  // 8.2 完善异常判断
  if (this.status === 'pendding') {
    // 8.3 补充异常队列
    this.rejectedQueues.push(function () {
      // 10.7 删除原来定义的self,当前函数的this指向的就是实例,为了防止和外层混淆,定义新的self
      var self = this;
      if (self.status === 'rejected' && reject) {
        reject(self.rejectData)
      }
    })
  } else if (this.status === 'rejected') {
    reject(this.rejectData)
  }
}

// 测试用例1
new PromiseA(function (resolve, reject) {
  reject(1)
}).then(res => {
  console.log('成功' + res);
}, err => {
  console.log('失败' + err);
});

// 测试用例2
new PromiseA(function (resolve, reject) {
  setTimeout(function (){
    reject(2)
  }, 1000)
}).then(res => {
  console.log('成功' + res);
}, err => {
  console.log('失败' + err);
});

// 测试用例3
new PromiseA(function (resolve, reject) {
  reject(3)
}).then(res => {
  console.log('成功' + res);
}).catch(err => {
  console.log('失败' + err);
});

// 测试用例4
new PromiseA(function (resolve, reject) {
  setTimeout(function (){
    reject(4)
  }, 1000)
}).then(res => {
  console.log('成功' + res);
}).catch(err => {
  console.log('失败' + err);
});

// 测试用例5
new PromiseA(function (resolve, reject) {
  setTimeout(function (){
    resolve(5)
  }, 1000)
}).then(res => {
  console.log('成功' + res);
  return new PromiseA((resolve, reject) => {
    resolve(6)
  })
}).then(res => {
  console.log('成功' + res);
  return new PromiseA((resolve, reject) => {
    reject(7)
  })
}).then(res => {
  console.log('成功' + res);
}).catch(err => {
  console.log('失败' + err);
});

// 测试用例6
new PromiseA(function (resolve, reject) {
  setTimeout(function (){
    resolve(8)
  }, 1000)
}).then(res => {
  console.log('成功' + res);
  return new PromiseA((resolve, reject) => {
    setTimeout(function (){
      resolve(9)
    }, 1000)
  })
}).then(res => {
  console.log('成功' + res);
  return new PromiseA((resolve, reject) => {
    reject(10)
  })
}).then(res => {
  console.log('成功' + res);
}).catch(err => {
  console.log('失败' + err);
});

代码通篇拷贝,只需关注测试用例6步骤10,历史测试用例保留原因为修改PromiseA需保证没有影响到历史代码。

结语

上述最后一段代码就是Promise的js实现了,测试覆盖不是很全面,所以可能会有其他的问题。

用js实现Promise主要是加深对微任务,队列,this指向,构造函数,原型等等原生js的技术加深。

有兴趣的可以自己试一试