阅读 3370

进来看看ES6 Promise最全手写实现

写在开头

  • 这几天看到有些技术群在发Promise相关的一些实现,自己最近也在看ES6的一些内容,于是打算自己也整理一下,提升一下理解;
  • 本文适合一些了解并使用过Promise的人,如果你没有了解或使用过Promise,建议先看一下 阮一峰 ECMAScript6 入门 之Promise

什么是Promise

  • 异步编程的一种解决方案;
  • Promise是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果;

特点

  • 对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。
  • 一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected

简单实现

流程分析

  • 图片来源 MDN

初始化一个Promise

  • 原始的Promise
// Promise构造函数接收一个executor函数,executor函数执行完同步或异步操作后,调用它的两个参数resolve和reject
const promise = new Promise(function(resolve, reject) {
  /*
    如果操作成功,调用resolve并传入value
    如果操作失败,调用reject并传入reason
  */
})
复制代码
  • 包含当前的状态
  • 当前的值
  • fulfilled的回调函数数组
  • rejected的回调函数数组
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';

const NewPromise = function(executor) {
  const _this = this;
  _this.status = PENDING; // 状态
  _this.data = undefined; // 值
  _this.onResolvedCallback = []; // fulfilled的回调函数数组
  _this.onRejectedCallback = []; // rejected的回调函数数组
  // 成功
  function resolve(value) { ... }
  // 失败
  function reject(reason) { ... }
  try {
    executor(resolve, reject);
  } catch (error) {
    reject(error);
  }
}  
复制代码

完善resolereject

  • resole是成功的调用,需要不状态修改为fulfilled,然后当前的值设为传入的值,遍历执行onResolvedCallback中的回调。
// 成功
function resolve(value) {
    只有pending状态才能转换为fulfilled / rejected
    setTimeout(function() { // 异步执行所有的回调函数
        if (_this.status === PENDING) {
            _this.status = FULFILLED;
            _this.data = value;
            _this.onResolvedCallback.forEach(callback => callback(value));
        }
    });
}
复制代码
  • reject的实现和resolve类似,只是状态修改为rejected及执行onRejectedCallback
// 失败
function reject(reason) {
    setTimeout(function() { // 异步执行所有的回调函数
        if (_this.status === PENDING) {
            _this.status = REJECTED;
            _this.data = reason;
            _this.onRejectedCallback.forEach(callback => callback(reason));
        }
    });
}
复制代码
  • 现在已经实现了基本的Promise,你可以像使用Promise一样使用它,但还没有添加其他的方法。
// 测试
const promise = new NewPromise(function(resolve, reject) {
  console.log('ss', 11)
})
复制代码

then方法

  • Promise对象有一个then方法,用来注册在这个Promise状态确定后的回调,then方法需要写在原型链上(为什么要写在原型上不清楚的可能需要补一下JavaScript基础了)。在Promise/A标准中,明确规定了then要返回一个新的对象,所以在我们的实现中也返回一个新对象。
// then挂载到原型上
NewPromise.prototype.then = function(onResolved, onRejected) {
  const _this = this;
  if ( typeof onResolved !== 'function') {
   onResolved = function(value) { return value }
  }
  if ( typeof onRejected !== 'function') {
    onRejected = function(reason) { throw reason }
  }

  // 公共判断
  const common = function (data, resolve, reject) {
    // 考虑到有可能throw,我们将其包在try/catch块里
    try {
      let value = _this.status === FULFILLED
        ? onResolved(data)
        : onRejected(data)
      if( value instanceof Promise) {
        value.then(resolve, reject)
      }
      resolve(value)
    } catch (error) {
      reject(error)
    }
  }
  // 公共判断
  const pendingCommon = function (data, flag, resolve, reject) {
    // 考虑到有可能throw,我们将其包在try/catch块里
    try {
      let value = flag === FULFILLED
        ? onResolved(data)
        : onRejected(data)
      if( value instanceof Promise) {
        value.then(resolve, reject)
      }
      resolve(value)
    } catch (error) {
      reject(error)
    }
  }

  if (_this.status === PENDING) {
    return new NewPromise(function(resolve, reject) {
      _this.onResolvedCallback.push((value) => {
        pendingCommon(value, FULFILLED, resolve, reject);
      })

      _this.onRejectedCallback.push((reason) => {
        pendingCommon(reason, REJECTED, resolve, reject);
      })
    })
  } else { // resolve / reject
    return new NewPromise(function (resolve, reject) {
      setTimeout(function () {
        common(_this.data, resolve, reject)
      })
    })
  }
}
复制代码

resolve方法再次完善

  • 判断rosolve的值是否是Promise,如果是Promise继续执行.then方法。
// 成功
function resolve(value) { 
    // value 如果是Promise继续执行.then
+   if (value instanceof Promise) {
+     return value.then(resolve, reject)
+   }
    ...
}
复制代码

catch方法

  • 捕获Promise的异常错误。
// catch方法
NewPromise.prototype.catch = function(onRejected) {
  return this.then(null, onRejected)
}
复制代码

finally方法

  • finally()方法用于指定不管Promise对象最后状态如何,都会执行的操作。
// finally
NewPromise.prototype.finally = function (fun) {
  return this.then((value) => {
      return NewPromise.resolve(fun()).then(() => value);
  }, (err) => {
      return NewPromise.resolve(fun()).then(() => {
          throw err;
      });
  });
};
复制代码

resolve方法

  • 现有对象转为Promise对象,实例的状态为fulfilled
NewPromise.resolve = function(value) {
  if (value instanceof Promise) return value;
    if (value === null) return null;
    // 判断如果是promise
    if (typeof value === 'object' || typeof value === 'function') {
        try {
            // 判断是否有then方法
            let then = value.then;
            if (typeof then === 'function') {
                return new NewPromise(then.call(value)); // 执行value方法
            }
        } catch (e) {
            return new NewPromise( (resolve, reject) =>{
                reject(e);
            });
        }
    }
    return new NewPromise( (resolve, reject) =>{
      resolve(value);
  });
}
复制代码

reject方法

  • 现有对象转为Promise对象,实例的状态为rejected。实现和resolve方法类似,最后return改为reject
NewPromise.reject = function(reason) {
    ...
    return new NewPromise( (resolve, reject) =>{
      reject(reason);
  });
}
复制代码

all方法

  • Promise.all方法用于将多个 Promise 实例,包装成一个新的Promise 实例;方法的参数可以不是数组,但必须具有Iterator接口,且返回的每个成员都是Promise实例。
// all
NewPromise.all = function(promises) {
  return new NewPromise((resolve, reject) => {
    const result = []
    // 判断参数属否是数组
    promises = Array.isArray(promises) ? promises : []
    const len = promises.length;
    if(len === 0) resolve([]);
    let count = 0
    for (let i = 0; i < len; ++i) {
      if(promises[i] instanceof Promise) {
          promises[i].then(value => {
            count ++
            result[i] = value
            if (count === len) resolve(result)
          }, reject)
      } else {
        count ++
        result[i] = promises[i]
        if (count === len) resolve(result)
      }
    }
  }) 
}
复制代码

race方法

  • Promise.race()方法同样是将多个Promise实例,包装成一个新的Promise实例。
  • 只要参数之中有一个实例率先改变状态,Promise.race()的状态就跟着改变。那个率先改变的Promise实例的返回值,就传递给Promise.race()的回调函数。
// race
NewPromise.race = function (promises) {
  return new NewPromise((resolve, reject) => {
    promises = Array.isArray(promises) ? promises : []
    promises.forEach(promise => {
      promise.then(resolve, reject)
    })
  })
}
复制代码

deferred方法

  • 返回一个有promise方法的对象。
// defered
NewPromise.deferred = function() {
  const dfd = {}
  dfd.promise = new NewPromise(function(resolve, reject) {
    dfd.resolve = resolve
    dfd.reject = reject
  })
  return dfd
}
复制代码

allSettled方法

  • Promise.allSettled()方法接受一组Promise实例作为参数,包装成一个新的Promise实例,只有等到所有这些参数实例都返回结果,不管是fulfilled还是rejected,包装实例才会结束。返回一个包含所有结果的数组。
NewPromise.allSettled = function (promises) {
  return new NewPromise((resolve, reject) => {
    promises = Array.isArray(promises) ? promises : []
    let len = promises.length;
    const argslen = len;
    if (len === 0) return resolve([]);
    let args = Array.prototype.slice.call(promises);
    function resolvePromise(index, value) {
      if(typeof value === 'object') { // 传入的是否是object
        const then = value.then;
        if(typeof then === 'function'){
          then.call(value, function(val) {
            args[index] = { status: 'fulfilled', value: val};
            if(--len === 0) {
              resolve(args);
            }
          }, function(e) {
            args[index] = { status: 'rejected', reason: e };
            if(--len === 0) {
              reject(args);
            }
          })
        }
      }
    }
 
    for(let i = 0; i < argslen; i++){
      resolvePromise(i, args[i]);
    }
  })
}
复制代码

测试

基础测试

// 测试
const promise = new NewPromise(function(resolve, reject) {
  console.log('ss', 11);
  resolve(123);
})
复制代码

then测试

promise.then(val => {
  console.log('val', val)
})
复制代码

catch测试

const promise = new NewPromise(function(resolve, reject) {
  console.log('ss', 11);
  reject('errr')
})
promise.catch(err => {
  console.log('err', err)
})
复制代码

finally测试

const resolved = NewPromise.resolve(1);
const rejected = NewPromise.reject(-1);
const resolved1 = NewPromise.resolve(17);

const p = NewPromise.all([resolved, resolved1, rejected]);
p.then((result) => {
  console.log('result', result)
}).catch(err => {
  console.log('err', err)
}).finally(() => {
  console.log('finally')
})
复制代码

resolve测试

const resolved = NewPromise.resolve(1);
resolved.then(val => {
    console.log('resolved', val)
})
复制代码

reject测试

const rejected = NewPromise.reject(-1);
rejected.catch(val => {
  console.log('rejected', val)
})
复制代码

all测试

const resolved = NewPromise.resolve(1);
const rejected = NewPromise.reject(-1);
const resolved1 = NewPromise.resolve(17);

const p = NewPromise.all([resolved, resolved1, rejected]);
p.then((result) => {
  console.log('result', result)
}).catch(err => {
  console.log('err', err)
})
复制代码

allSettled测试

const resolved = NewPromise.resolve(1);
const rejected = NewPromise.reject(-1);
const resolved1 = NewPromise.resolve(17);

const p = NewPromise.allSettled([resolved, resolved1, rejected]);
p.then((result) => {
  console.log('result', result)
})
复制代码

全部代码

/*
 * @Author: detanx 
 * @Date: 2020-05-11 17:39:52 
 * @Last Modified by:   detanx 
 * @Last Modified time: 2020-05-11 17:39:52 
 */
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';

const NewPromise = function(executor) {
  const _this = this;
  _this.status = PENDING;
  _this.data = undefined;
  _this.onResolvedCallback = [];
  _this.onRejectedCallback = [];

  // 成功
  function resolve(value) {
    if (value instanceof Promise) {
      return value.then(resolve, reject)
    }
    setTimeout(function() { // 异步执行所有的回调函数
      if (_this.status === PENDING) {
        _this.status = FULFILLED;
        _this.data = value;
        _this.onResolvedCallback.forEach(callback => callback(value));
      }
    })
  }
  // 失败
  function reject(reason) {
    setTimeout(function() { // 异步执行所有的回调函数
      if (_this.status === PENDING) {
        _this.status = REJECTED;
        _this.data = reason;
        _this.onRejectedCallback.forEach(callback => callback(reason));
      }
    })
  }

  try {
    executor(resolve, reject);
  } catch (error) {
    reject(error);
  }
}  
// then
NewPromise.prototype.then = function(onResolved, onRejected) {
  const _this = this;
  if ( typeof onResolved !== 'function') {
   onResolved = function(value) { return value }
  }
  if ( typeof onRejected !== 'function') {
    onRejected = function(reason) { throw reason }
  }

  // 公共判断
  const common = function (data, resolve, reject) {
    // 考虑到有可能throw,我们将其包在try/catch块里
    try {
      let value = _this.status === FULFILLED
        ? onResolved(data)
        : onRejected(data)
      if( value instanceof Promise) {
        value.then(resolve, reject)
      }
      resolve(value)
    } catch (error) {
      reject(error)
    }
  }
  // 公共判断
  const pendingCommon = function (data, flag, resolve, reject) {
    // 考虑到有可能throw,我们将其包在try/catch块里
    try {
      let value = flag === FULFILLED
        ? onResolved(data)
        : onRejected(data)
      if( value instanceof Promise) {
        value.then(resolve, reject)
      }
      resolve(value)
    } catch (error) {
      reject(error)
    }
  }

  if (_this.status === PENDING) {
    return new NewPromise(function(resolve, reject) {
      _this.onResolvedCallback.push((value) => {
        pendingCommon(value, FULFILLED, resolve, reject);
      })

      _this.onRejectedCallback.push((reason) => {
        pendingCommon(reason, REJECTED, resolve, reject);
      })
    })
  } else { // resolve / reject
    return new NewPromise(function (resolve, reject) {
      setTimeout(function () {
        common(_this.data, resolve, reject)
      })
    })
  }
}
NewPromise.resolve = function(value) {
  if (value instanceof Promise) return value;
    if (value === null) return null;
    // 判断如果是promise
    if (typeof value === 'object' || typeof value === 'function') {
        try {
            // 判断是否有then方法
            let then = value.then;
            if (typeof then === 'function') {
                return new NewPromise(then.call(value)); // 执行value方法
            }
        } catch (e) {
            return new NewPromise( (resolve, reject) =>{
                reject(e);
            });
        }
    }
    return new NewPromise( (resolve, reject) =>{
      resolve(value);
  });
}
NewPromise.reject = function(value) {
  if (value instanceof Promise) return value;
    if (value === null) return null;
    // 判断如果是promise
    if (typeof value === 'object' || typeof value === 'function') {
        try {
            // 判断是否有then方法
            let then = value.then;
            if (typeof then === 'function') {
                return new NewPromise(then.call(value)); // 执行value方法
            }
        } catch (e) {
            return new NewPromise( (resolve, reject) =>{
                reject(e);
            });
        }
    }
    return new NewPromise( (resolve, reject) =>{
      reject(value);
  });
}
// catch方法
NewPromise.prototype.catch = function(onRejected) {
  return this.then(null, onRejected)
}
// finally
NewPromise.prototype.finally = function (fun) {
  return this.then((value) => {
        return NewPromise.resolve(fun()).then(() => value);
    }, (err) => {
      return NewPromise.resolve(fun()).then(() => {
        throw err;
    });
  });
};
// defered
NewPromise.deferred = function() {
  const dfd = {}
  dfd.promise = new NewPromise(function(resolve, reject) {
    dfd.resolve = resolve
    dfd.reject = reject
  })
  return dfd
}
// all
NewPromise.all = function(promises) {
  return new NewPromise((resolve, reject) => {
    const result = []
    promises = Array.isArray(promises) ? promises : []
    const len = promises.length;
    if(len === 0) resolve([]);
    let count = 0
    for (let i = 0; i < len; ++i) {
      if(promises[i] instanceof Promise) {
          promises[i].then(value => {
            count ++
            result[i] = value
            if (count === len) resolve(result)
          }, reject)
      } else {
        result[i] = promises[i]
      }
    }
  }) 
}
// race
NewPromise.race = function (promises) {
  return new NewPromise((resolve, reject) => {
    promises = Array.isArray(promises) ? promises : []
    promises.forEach(promise => {
      promise.then(resolve, reject)
    })
  })
}
// allSettled
NewPromise.allSettled = function (promises) {
  return new NewPromise((resolve, reject) => {
    promises = Array.isArray(promises) ? promises : []
    let len = promises.length;
    const argslen = len;
    if (len === 0) return resolve([]);
    let args = Array.prototype.slice.call(promises);
    function resolvePromise(index, value) {
      if(typeof value === 'object') {
        const then = value.then;
        if(typeof then === 'function'){
          then.call(value, function(val) {
            args[index] = { status: 'fulfilled', value: val};
            if(--len === 0) {
              resolve(args);
            }
          }, function(e) {
            args[index] = { status: 'rejected', reason: e };
            if(--len === 0) {
              reject(args);
            }
          })
        }
      }
    }
 
    for(let i = 0; i < argslen; i++){
      resolvePromise(i, args[i]);
    }
  })
}

// 测试
// const promise = new NewPromise(function(resolve, reject) {
//   console.log('ss', 11)
//   // resolve(123)
//   reject('errr')
//   // throw 'ree'
// })
// promise.catch(val => {
//   console.log('val', val)
// })
// const promise1 = new NewPromise(function(resolve, reject) {
//   console.log('ss', 11)
//   resolve(123)
//   // reject('errr')
// })
// const rejected = NewPromise.reject(-1);
// rejected.catch(val => {
//     console.log('rejected', val)
//   })
// console.log('resolved', resolved)
const resolved = NewPromise.resolve(1);
const rejected = NewPromise.reject(-1);
const resolved1 = NewPromise.resolve(17);

const p = NewPromise.all([resolved, resolved1, rejected]);
p.then((result) => {
  console.log('result', result)
}).catch(err => {
  console.log('err', err)
})
复制代码

参考

阮一峰 ECMAScript 6 入门
22 道高频 JavaScript 手写面试题及答案

  • 有帮到您的话留下你的赞👍呗,你的支持就是我产出的动力。

补充

Promise.all存在疑

  • 测试了原生的Promise.all方法。
  • 当所有的fulfilled

修改all方法异步成功返回顺序

  • 上面的all方法代码已更新。
  • 测试用例,感谢 AshoneA 提出的问题。
const promise1 = new Promise(resolve => {
  setTimeout(() => {
    resolve("promise1");
  }, 200);
});
const promise2 = new Promise(resolve => {
  setTimeout(() => {
    resolve("promise2");
  }, 100);
});
NewPromise.all([promise1, promise2]).then(value => {
  console.log(value);
});

Promise.all([promise1, promise2]).then(value => {
  console.log(value);
});
复制代码
  • 之前测试结果。测试地址
  • 修改后测试结果。