更好理解Promise日常使用从手写PromiseA+开始

584 阅读8分钟

Promise

目的:为了更好使用Promise日常使用。

Promise概念

Promise是JavaScript的内置对象,同时也是一个构造函数
特别说明:Promise构造函数是为了解决异步问题;同步代码中也可以使用(大材小用)。

作为内置对象

静态方法

  • Promise.all(iterable)
  • Promise.allSettled(iterable)
  • Promise.any(iterable)
  • Promise.race(iterable)
  • Promise.reject(reason)
  • Promise.resolve(value)

作为构造函数

Promise.prototype

  • Promise.prototype.constructor
  • Promise.prototype.then(onFulfilled, onRejected)
    • onFulfilled为可选参数
    • onRejected为可选参数
  • Promise.prototype.catch(onRejected)
    • onRejected为可选参数
  • Promise.prototype.finally(onFinally)
    • onFinally为可选参数

Promise实例

const promise = new Promise(function(resolve, reject) {
  // ...省略代码
  if (/* 异步操作成功 */){
    resolve(value);
  } else {
    reject(error);
  }
});
关于executor函数、resolve函数、reject函数的说明
  • Promise构造函数接受一个executor函数作为参数
  • executor函数的两个参数分别是resolve函数和reject函数
    • resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;
    • reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
promise.then(function(value) {
  // success
}, function(error) {
  // failure
});
关于then方法说明
  • Promise实例生成以后,可以用then方法分别指定fulfilled状态和rejected状态的回调函数。
  • then方法可以接受两个参数。
    • 参数可选:onFulfilled 和 onRejected 都是可选参数,不一定要提供。
    • 如果 onFulfilled 不是函数,其必须被忽略
    • 如果 onRejected 不是函数,其必须被忽略
    • 如果 onFulfilled 是函数:
      • 当 promise 执行结束后其必须被调用,其第一个参数为 promise 的终值value
      • 在 promise 执行结束前其不可被调用
      • 其调用次数不可超过一次
    • 如果 onRejected 是函数:
      • 当 promise 被拒绝执行后其必须被调用,其第一个参数为 promise 的据因reason
      • 在 promise 被拒绝执行前其不可被调用
      • 其调用次数不可超过一次
  • then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)。因此可以采用链式写法,即then方法后面再调用另一个then方法。

手写Promise构造函数更好理解Promise(PromiseA+标准)

特别说明:通过类实现Promise构造函数

1. 实现executor函数、resolve函数、reject函数

class Promise{
  // 构造器
  constructor(executor){
    // 成功
    let resolve = () => { };
    // 失败
    let reject = () => { };
    // 立即执行executor函数
    executor(resolve, reject);
  }
}

2.实现Promise构造函数基本状态(state)

  • Promise构造函数三种状态
    • pending(进行中)
    • fulfilled(已成功)
    • rejected(已失败)
  • Promise构造函数状态切换
    • pending(等待态)为初始态
    • 通过resolve函数可以转化为fulfilled(成功态)
      • 成功时,不可转为其他状态,且必须有一个不可改变的值(value)
    • 通过reject函数可以转化为rejected(失败态)
      • 失败时,不可转为其他状态,且必须有一个不可改变的原因(reason)
    • 若是executor函数报错 直接执行reject函数
  • Promise构造函数几个关键属性
    • state: 状态描述
    • value: 成功值
    • reason: 失败原因
class Promise{
  constructor(executor){
    // 初始化state为等待态(state状态只能改变一次)
    // 因为只有状态为pending时候可以改变
    this.state = 'pending';
    // 成功初始值
    this.value = undefined;
    // 失败原因初始值
    this.reason = undefined;
    
    // resolve函数、reject函数
    const resolve = (value) => {
      // 如果state不为pending,下面条件为false,块级作用域代码不再执行
      if (this.state === 'pending') {
        // resolve调用后,state转化为fulfilled成功态
        this.state = 'fulfilled';
        // 更新成功的值
        this.value = value;
      }
    };
    const reject = (reason) => {
      // 如果state不为pending,下面条件为false,块级作用域代码不再执行
      if (this.state === 'pending') {
        // reject调用后,state转化为rejected失败态
        this.state = 'rejected';
        // 更新失败的原因
        this.reason = reason;
      }
    };
    
    // 如果executor执行报错,直接执行reject函数,错误信息作为入参传给reject函数
    try{
      executor(resolve, reject);
    } catch (error) {
      reject(error);
    }
  }
}

3.实现Promise实例的then方法(Promise.prototype.then)

  • then的方法接收两个参数(可选):onFulfilled函数和onRejected函数
  • onFulfilled函数的参数为成功值value
  • onRejected函数的参数为失败原因reason

executor函数同步执行

class Promise{
  constructor(executor){ 
      // ...省略代码
  }
  // then方法有两个参数onFulfilled函数和onRejected函数
  then(onFulfilled,onRejected) {
    // 状态为fulfilled,执行onFulfilled函数,传入成功的值
    if (this.state === 'fulfilled') {
      onFulfilled(this.value);
    };
    // 状态为rejected,执行onRejected函数,传入失败的原因
    if (this.state === 'rejected') {
      onRejected(this.reason);
    };
  }
}

executor函数在异步中调用resolve函数或reject函数

class Promise{
  constructor(executor){
    this.state = 'pending';
    this.value = undefined;
    this.reason = undefined;
    // onFulfilled函数回调数组
    this.onResolvedCallbacks = [];
    // onRejected函数回调数组
    this.onRejectedCallbacks = [];
    const resolve = (value) => {
      if (this.state === 'pending') {
        this.state = 'fulfilled';
        this.value = value;
        // 一旦resolve执行,调用onFulfilled函数回调数组
        this.onResolvedCallbacks.forEach(fn=>fn());
      }
    };
    const reject = (reason) => {
      if (this.state === 'pending') {
        this.state = 'rejected';
        this.reason = reason;
        // 一旦reject执行,onRejected函数回调数组
        this.onRejectedCallbacks.forEach(fn=>fn());
      }
    };
    try{
      executor(resolve, reject);
    } catch (err) {
      reject(err);
    }
  }
  then(onFulfilled,onRejected) {
    if (this.state === 'fulfilled') {
      onFulfilled(this.value);
    };
    if (this.state === 'rejected') {
      onRejected(this.reason);
    };
    // 当状态state为pending时
    if (this.state === 'pending') {
      // onFulfilled函数传入onFulfilled函数回调数组
      this.onResolvedCallbacks.push(()=>{
        onFulfilled(this.value);
      })
      // onRejected传入onRejected函数回调数组
      this.onRejectedCallbacks.push(()=>{
        onRejected(this.reason);
      })
    }
  }
}
  • 上述代码同时实现了同一个promise实例被多个then调用问题

    // 多个then的情况
    const p = new Promise();
    p.then();
    p.then();
    

4.实现Promise实例的链式调用

调用Promise构造函数原型对象的then、catch、finally都返回新的promise实例

class Promise{
  constructor(executor){
    // ...省略代码
  }
  then(onFulfilled,onRejected) {
    // 返回的promise2
    const promise2 = new Promise((resolve, reject)=>{
      if (this.state === 'fulfilled') {
        const x = onFulfilled(this.value);
        // resolvePromise函数,处理onFulfilled函数return的x和默认的promise2的关系
        resolvePromise(promise2, x, resolve, reject);
      };
      if (this.state === 'rejected') {
        const x = onRejected(this.reason);
        // resolvePromise函数,处理onRejected函数return的x和默认的promise2的关系
        resolvePromise(promise2, x, resolve, reject);
      };
      if (this.state === 'pending') {
        this.onResolvedCallbacks.push(()=>{
          const x = onFulfilled(this.value);
          resolvePromise(promise2, x, resolve, reject);
        })
        this.onRejectedCallbacks.push(()=>{
          const x = onRejected(this.reason);
          resolvePromise(promise2, x, resolve, reject);
        })
      }
    });
    // 返回promise,完成链式
    return promise2;
  }
}
resolvePromise函数实现
  1. 如果promisex 指向同一对象(x === promise2),会造成循环引用,自己等待自己完成,则报“循环引用”错误以 TypeError 为据因拒绝执行 promise
const p = new Promise(resolve => {
  resolve('success');
});
const p1 = p.then(data => {
  // 循环引用,自己等待自己完成,一辈子完不成
  return p1;
})
  1. 比较x和promise2,避免循环应用
  2. 如果 x== nulltypeof x !== 'object'typeof x !== 'function'直接resolv(x)
  3. 如果x != null && (typeof x === 'object' || typeof x === 'function')
    • x不是promise,直接resolv(x)
    • x是promise直接调用then方法
function resolvePromise(promise2, x, resolve, reject){
  // 循环引用报错
  if(x === promise2){
    // reject报错
    return reject(new TypeError('Chaining cycle detected for promise'));
  }
  // 防止多次调用
  let called;
  // x不是null 且x是对象或者函数
  if (x != null && (typeof x === 'object' || typeof x === 'function')) {
    try {
      // A+规定,声明then = x的then方法
      const then = x.then;
      // 如果then是函数,就默认是promise了
      if (typeof then === 'function') { 
        // 就让then执行第一个参数是this后面是成功的回调和失败的回调
        // then方法中this指向x
        then.call(x, y => {
          // 成功和失败只能调用一个
          if (called) return;
          called = true;
          // resolve的结果依旧是promise 那就继续解析
          resolvePromise(promise2, y, resolve, reject);
        }, err => {
          // 成功和失败只能调用一个
          if (called) return;
          called = true;
          reject(err);// 失败了就直接执行reject函数
        })
      } else {
        resolve(x); // 直接成功即可
      }
    } catch (e) {
      // 也属于失败
      if (called) return;
      called = true;
      // 取then出错了那就不要在继续执行了
      reject(e); 
    }
  } else {
    resolve(x);
  }
}

5.完善then方法中onFulfilled函数和onRejected函数

  • 1、PromiseA+规定onFulfilled,onRejected都是可选参数,如果他们不是函数,必须被忽略
    • onFulfilled是一个普通的值,成功时直接等于 value => value
    • onRejected返回一个普通的值,失败时如果直接等于 value => value,则会跑到下一个then中的onFulfilled中,所以直接扔出一个错误reason => throw err
  • 4、PromiseA+规定onFulfilled或onRejected不能同步被调用,必须异步调用。我们就用setTimeout解决异步问题
  • 5、如果onFulfilled或onRejected报错,则直接返回reject()
class Promise{
  constructor(executor){
      // ...省略代码
  }
  then(onFulfilled,onRejected) {
    // onFulfilled如果不是函数,就忽略onFulfilled,直接返回value
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
    // onRejected如果不是函数,就忽略onRejected,直接扔出错误
    onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err };
    let promise2 = new Promise((resolve, reject) => {
      if (this.state === 'fulfilled') {
        // 异步
        setTimeout(() => {
          try {
            let x = onFulfilled(this.value);
            resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            reject(e);
          }
        }, 0);
      };
      if (this.state === 'rejected') {
        // 异步
        setTimeout(() => {
          // 如果报错
          try {
            let x = onRejected(this.reason);
            resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            reject(e);
          }
        }, 0);
      };
      if (this.state === 'pending') {
        this.onResolvedCallbacks.push(() => {
          // 异步
          setTimeout(() => {
            try {
              let x = onFulfilled(this.value);
              resolvePromise(promise2, x, resolve, reject);
            } catch (e) {
              reject(e);
            }
          }, 0);
        });
        this.onRejectedCallbacks.push(() => {
          // 异步
          setTimeout(() => {
            try {
              let x = onRejected(this.reason);
              resolvePromise(promise2, x, resolve, reject);
            } catch (e) {
              reject(e);
            }
          }, 0)
        });
      };
    });
    // 返回promise,完成链式
    return promise2;
  }
}

6.实现catch和resolve、reject、race、all方法

class Promise{
  constructor(executor){
    this.state = 'pending';
    this.value = undefined;
    this.reason = undefined;
    this.onResolvedCallbacks = [];
    this.onRejectedCallbacks = [];
    let resolve = value => {
      if (this.state === 'pending') {
        this.state = 'fulfilled';
        this.value = value;
        this.onResolvedCallbacks.forEach(fn=>fn());
      }
    };
    let reject = reason => {
      if (this.state === 'pending') {
        this.state = 'rejected';
        this.reason = reason;
        this.onRejectedCallbacks.forEach(fn=>fn());
      }
    };
    try{
      executor(resolve, reject);
    } catch (err) {
      reject(err);
    }
  }
  then(onFulfilled,onRejected) {
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
    onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err };
    let promise2 = new Promise((resolve, reject) => {
      if (this.state === 'fulfilled') {
        // 可以用queueMicrotask替换setTimeout
        setTimeout(() => {
          try {
            const x = onFulfilled(this.value);
            resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            reject(e);
          }
        }, 0);
      };
      if (this.state === 'rejected') {
        setTimeout(() => {
          try {
            const x = onRejected(this.reason);
            resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            reject(e);
          }
        }, 0);
      };
      if (this.state === 'pending') {
        this.onResolvedCallbacks.push(() => {
          setTimeout(() => {
            try {
              const x = onFulfilled(this.value);
              resolvePromise(promise2, x, resolve, reject);
            } catch (e) {
              reject(e);
            }
          }, 0);
        });
        this.onRejectedCallbacks.push(() => {
          setTimeout(() => {
            try {
              const x = onRejected(this.reason);
              resolvePromise(promise2, x, resolve, reject);
            } catch (e) {
              reject(e);
            }
          }, 0)
        });
      };
    });
    return promise2;
  }
  // catch方法
  catch(fn){
    return this.then(null,fn);
  }
}
function resolvePromise(promise2, x, resolve, reject){
  if(x === promise2){
    return reject(new TypeError('Chaining cycle detected for promise'));
  }
  let called;
  if (x != null && (typeof x === 'object' || typeof x === 'function')) {
    try {
      let then = x.then;
      if (typeof then === 'function') { 
        then.call(x, y => {
          if(called)return;
          called = true;
          resolvePromise(promise2, y, resolve, reject);
        }, err => {
          if(called)return;
          called = true;
          reject(err);
        })
      } else {
        resolve(x);
      }
    } catch (e) {
      if(called)return;
      called = true;
      reject(e); 
    }
  } else {
    resolve(x);
  }
}
//resolve方法
Promise.resolve = function(val){
  return new Promise((resolve,reject)=>{
    resolve(val)
  });
}
//reject方法
Promise.reject = function(val){
  return new Promise((resolve,reject)=>{
    reject(val)
  });
}
//race方法 
Promise.race = function(promises){
  return new Promise((resolve,reject)=>{
    for(let i=0;i<promises.length;i++){
      promises[i].then(resolve,reject)
    };
  })
}
//all方法(获取所有的promise,都执行then,把结果放到数组,一起返回)
Promise.all = function(promises){
  let arr = [];
  let i = 0;
  function processData(index,data){
    arr[index] = data;
    i++;
    if(i == promises.length){
      resolve(arr);
    };
  };
  return new Promise((resolve,reject)=>{
    for(let i=0;i<promises.length;i++){
      promises[i].then(data=>{
        processData(i,data);
      },reject);
    };
  });
}

Promise配合async/await使用

let result = true;
// 从服务器接口获取数据
async function getServerData(){
    // 模拟请求
    const p = new Promise((resolve,reject)=>{
        setTimeout(() => {
            if(result){
                resolve('data')
            }else {
                reject('err')
            }
        }, 1000);
    })
    return p
}
// 供前端调用接口
async function dataController() {
    try {
       const data = await getServerData();
       console.log('data',data);
    } catch (error) {
        // 可以捕获reject
        console.log('error', error);
    }
}
// 前端接口
dataController()
  • 特别说明try...catchcatch可以捕获reject函数的reason

参考文章

BAT前端经典面试问题:史上最最最详细的手写Promise教程

关于Generator函数