手写promise,你学会了吗?

123 阅读12分钟

promise A+规范官网:promisesaplus.com/

一、Promise的介绍


Promise,译为承诺,是异步编程的一种解决方案,比传统的解决方案(回调函数)更加合理和更加强大

在以往我们如果处理多层异步操作,我们往往会像下面那样编写我们的代码

doSomething(function(result) {
  doSomethingElse(result, function(newResult) {
    doThirdThing(newResult, function(finalResult) {
      console.log('得到最终结果: ' + finalResult);
    }, failureCallback);
  }, failureCallback);
}, failureCallback);

阅读上面代码,是不是很难受,上述形成了经典的回调地狱

现在通过Promise的改写上面的代码

doSomething().then(function(result) {
  return doSomethingElse(result);
})
.then(function(newResult) {
  return doThirdThing(newResult);
})
.then(function(finalResult) {
  console.log('得到最终结果: ' + finalResult);
})
.catch(failureCallback);

瞬间感受到promise解决异步操作的优点

  • 链式操作减低了编码难度
  • 代码可读性明显增强

状态

promise对象仅有三种状态

  • pending(进行中)
  • fulfilled(已成功)
  • rejected(已失败)

pending

  • 初始化状态,可改变
  • 一个promise在resolve和reject前都处于这个状态
  • 可以通过resolve --> fulfilled状态
  • 可以通过reject --> rejected状态

fulfilled

  • 最终态,不可变
  • promise被resolve后会变成这个状态
  • 必须拥有一个value值,promise成功后返回的值

rejected

  • 最终态,不可变
  • promise被reject后变成这个状态
  • 必须拥有reason,promise失败后必须有一个错误的理由

ps:“不可变”意味着不变的身份,(即===),并不意味着深刻的不变性

promise的状态流转:

  • pending --> resolve(value) --> fulfilled
  • pending --> reject(reason) --> rejected

then 函数

promise必须提供一个then方法来访问当前或最终的值(value)或原因(reason)。

then接收两个参数:onFulfilled、onRejected

promise.then(onFulfilled,onRejected)

onFulfilled & onRejected

  1. onFulfilled、onReject两个参数如果不是函数,则会忽略。
  2. promise状态变成fulfilled或rejected前,onFulfilled或onReject不能调用且不能被多次调用
  3. 在 promise变成 fulfilled或rejected状态 的时候,应该调用 onFulfilled或onReject;
  4. onFulfilled的参数是value,onRejected的参数是reason
  5. onFulfilled和onRejected应该是微任务 可以用queueMicrotask 或者 setTimeout来实现微任务的调用
  6. onFulfilled和onRejected必须在执行后才能被调用,并且只能调用一次。

then可以被调用多次

  1. promise状态变成fulfilled后,所有的onFulfilled回调都需要按照then的顺序执行,也就是按照注册顺序执行(在实现的时候需要一个数组来存放多个onFulfilled的回调)
  2. promise状态变成rejected后,所有的onRejected回调都需要按照then的顺序执行,也就是按照注册顺序执行(在实现的时候需要一个数组来存放多个onFulfilled的回调)

then返回值

then应该返回一个Promise,是一个新的Promise

promise2 = promise1.then(onFulfilled,onRejected)
  1. onFulfilled或onRejected执行的结果为x,调用resolvePromise
  2. 如果onFulfilled或onRejected执行时抛出异常,promise2需要被rejected。原因就是reason。
  3. 如果onFulfiiled不是一个函数并且promise1为fulfilled,promise2会以promise1的value触发fulfilled
  4. 如果onRejected不是一个函数并且promise1为rejected,promise2会以promise1的reason触发rejected

Promise细节:

p.then()时,里面是可以接收两个函数的,一个是onFulfilled,另一个是onRejected,这时有些同志看了不免有两点疑惑,第一点疑惑是:then居然能接收两个函数?;第二个疑惑是:then如果能接收到onRejected,那为什么还需要catch?

then确实能接收到两个函数,但我们平时基本只会用到第一个onFulfilled;第二个onRejected和catch的区别是:onRejected只能处理发生在p的错误,而catch除了能处理p的错误,还能处理onFulfilled内部的错误以及p.then().then()....中每一环的错误,所以我们更习惯用catch

resolvePromise

resolvePromiseFn(promise2,x,resolve,reject)
  1. 如果promise2 === x,那么reject TypeError
  2. 如果x是一个promise:
  • x是pending状态,那么promise必须保持pending状态,直到x变成fulfilled或rejected
  • x被fulfilled,promise以相同的value返回
  • x被rejected,promise以相同的reason的理由拒绝
  1. 如果x是一个function或object
let then = x.then
  • 如果x.then出错,那么reject promise with e as the reason

  • 如果then是一个函数,then.call(x,resolovePromise,rejectPromise),因为是函数的原因,当前的then指向不是当前的上下文,要把当前函数的this指向当前上下文。相当于then.call(x) => x.then

  1. 如果resolvePromise的入参是y,执行resolvePromise(promise2,y,resolve,reject)
  2. 如果resolvePromise的入参是r,reject promise with r
  3. 如果resolvePromise和resolvePromise或者多次调用同一个参数,那么第一个调用优先,后面的调用忽略
  4. 如果调用then抛出异常e,如果resolvePromise或rejectPromise已被调用,就忽略。否则reject promise with e as the reason
  5. 如果then不是一个Function,fulfill promse with x

流程

认真阅读下图,我们能够轻松了解promise整个流程

术语

  1. promise 是一个有 then 方法的对象或者是函数,行为遵循本规范
  2. thenable 是一个有 then 方法的对象或者是函数
  3. valuepromise 状态成功时的值,也就是 resolve 的参数, 表示结果的数据
  4. reasonpromise 状态失败时的值, 也就是 reject 的参数, 表示拒绝的原因
  5. exception 是一个使用 throw 抛出的异常值

二、Promise用法

1、初探 promise

先不管是什么,但是你一定用过。

let a = getSomeThing('/api/v1/user/role');

a.then(res => {
    // vue
    this.dataList = res.data;
    // react
    setData(res.data)
});

// -------- 我们来用 promise 模拟一下这个接口 ---------

function getSomeThing(url) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve({ data: [1,2,3] })
        }, 2000);
    });
};
  • Promise 是一个构造函数。
  • Promise 接收一个函数作为参数,这个函数的参数,是两个函数。
  • Promise 返回的对象,包含一个 then 函数, 这个 then 函数,接收两个参数,这两个参数,也都是函数。

2、手写Promise

初探版

// executor执行函数
function LPromise(executor) {
  this.status = 'pending';
  this.value = null;
  this.reason = null;

  // 成功状态
  const resolve = value => {
    if (this.status == 'pending') {
      this.status = 'fulfilled';
      this.value = value;
    }
  };
  // 失败状态
  const reject = reason => {
    if (this.status == 'pending') {
      this.status = 'rejected';
      this.reason = reason;
    }
  };
  // 小优化:在执行executor时,也有可能代码报错,此时也应该进行reject
  try {
    executor(resolve, reject);
  } catch (err) {
    reject(err);
  }
}

LPromise.prototype.then = function (onFulfilled, onRejected) {
  onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : data => { return data; };
  onRejected = typeof onRejected === 'function' ? onRejected : data => { return data; };
  if (this.status == 'fulfilled') {
    onFulfilled(this.value);
  }
  if (this.status == 'rejected') {
    onRejected(this.reason);
  }
};

new LPromise((resolve, reject) => {
  setTimeout(() => {
      resolve('hello')
  },1000)
}).then(res => {
  console.log('res===', res);
});

以上有个问题:

问题在于,我们 resolve 的时候, then 函数,已经执行过了。

所以,我们要把函数收集起来在一个合适的时机去执行它

再换句话说,我们需要在一个合适的时间,去通知 onfulfilled 执行

异步问题说明

onfulfilledonrejected 应该是微任务(我们暂时用 setTimeout 代替)

function LPromise(executor) {
  this.status = 'pending';
  this.value = null;
  this.reason = null;
  this.onFulfilledArray = []; // 成功回调数组
  this.onRejectedArray = []; // 失败回调数组
  // 成功状态
  const resolve = value => {
    if (this.status == 'pending') {
      this.status = 'fulfilled';
      this.value = value;
      this.onFulfilledArray.forEach(func => func(value));
    }
  };
  // 失败状态
  const reject = reason => {
    if (this.status == 'pending') {
      this.status = 'rejected';
      this.reason = reason;
      this.onRejectedArray.forEach(func => func(reason));
    }
  };
  // 小优化:在执行executor时,也有可能代码报错,此时也应该进行reject
  try {
    executor(resolve, reject);
  } catch (err) {
    reject(err);
  }
}

LPromise.prototype.then = function (onFulfilled, onRejected) {
  onFulfilled =
    typeof onFulfilled === 'function'
      ? onFulfilled
      : data => {
          return data;
        };
  onRejected =
    typeof onRejected === 'function'
      ? onRejected
      : data => {
          return data;
        };
  if (this.status == 'fulfilled') {
    onFulfilled(this.value);
  }
  if (this.status == 'rejected') {
    onRejected(this.reason);
  }
  if (this.status == 'pending') {
    this.onFulfilledArray.push(onFulfilled);
    this.onRejectedArray.push(onRejected);
  }
};

const p = new LPromise((resolve, reject) => {
  setTimeout(() => {
    resolve('hello');
  }, 1000);
});

p.then(res => {
  console.log(res, '1111');
});
p.then(res => {
  console.log(res, '2222');
});
p.then(res => {
  console.log(res, '3333');
});
// 11   22  33

为什么要用数组?

  1. then 方法, 可以多次被调用
  2. promise 的状态变成 fulfilled / rejected 后。所有的 onfulfilled / onRejected 都会按照 then 的顺序执行,也就是 注册顺序执行。
const LPromise = require('./Promise2');

new LPromise((resolve, reject) => {
    setTimeout(() => {
        resolve('hello');
    },1000)
}).then(res => {
    console.log(res);
    return res + 'luyi'
}).then(res => {
    console.log(res);
})

then 方法,应该返回一个 promise

promise2 = promise1.then(onFulfilled, onRejected);

  • onFulfilled / onRejected 的执行结果 为 x, 调用 resolvePromise;
  • 如果 onFulfilled / onRejected 执行时抛出异常,我们的 promise2 需要被 reject
  • 如果 onFulfilled / onRejected 不是一个函数, promise2 以 promise1 的 value / reason 触发 fulfilled 和 rejected

重点:onfulfilled 返回了一个值,是 then 返回的 promise 需要 resolve 的。

处理链式调用

function LPromise(executor) {
  this.status = 'pending';
  this.value = null;
  this.reason = null;
  this.onFulfilledArray = []; // 成功回调数组
  this.onRejectedArray = []; // 失败回调数组
  // 成功状态
  const resolve = value => {
    if (this.status == 'pending') {
      this.status = 'fulfilled';
      this.value = value;
      this.onFulfilledArray.forEach(func => func(value));
    }
  };
  // 失败状态
  const reject = reason => {
    if (this.status == 'pending') {
      this.status = 'rejected';
      this.reason = reason;
      this.onRejectedArray.forEach(func => func(reason));
    }
  };
  // 小优化:在执行executor时,也有可能代码报错,此时也应该进行reject
  try {
    executor(resolve, reject);
  } catch (err) {
    reject(err);
  }
}

LPromise.prototype.then = function (onFulfilled, onRejected) {
  let p2 = new Promise((resolve, reject) => {
    // 新建一个Promise,并且返回
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : data => { return data; };
    onRejected = typeof onRejected === 'function' ? onRejected : data => { return data; };
    if (this.status == 'fulfilled') {
      // onFulfilled 成功的 .then 回调
      let x = onFulfilled(this.value);
      resolve(x);
    }
    if (this.status == 'rejected') {
      onRejected(this.reason);
    }
    if (this.status == 'pending') {
      this.onFulfilledArray.push(onFulfilled);
      this.onRejectedArray.push(onRejected);
    }
  });
  return p2;
};

const p = new LPromise((resolve, reject) => {
  resolve('hello');
});

p.then(res => {
  console.log(res, '1111');
  return 2;
}).then(res => {
  console.log(res, '2222');
  return 3;
}).then(res => {
  console.log(res, '3333');
});

处理循环调用问题

在真实的Promise中,我们按以下方式调用:

const p = new Promise((resolve, reject) => {
  resolve(1);
})

const p2 = p.then(res => {
  console.log(res);
  return p2;
})

运行时会发现抛Uncaught (in promise) TypeError: Chaining cycle detected for promise #异常,这是因为p2需要接收p.then的结果,而p2又作为p.then的结果返回,如此便陷入了死循环

但是Promise给我们做了这块异常处理,所以我们也需要在我们的代码中处理这块的异常

function LPromise(executor) {
  this.status = 'pending';
  this.value = null;
  this.reason = null;
  this.onFulfilledArray = []; // 成功回调数组
  this.onRejectedArray = []; // 失败回调数组
  // 成功状态
  const resolve = value => {
    if (this.status == 'pending') {
      this.status = 'fulfilled';
      this.value = value;
      this.onFulfilledArray.forEach(func => func(value));
    }
  };
  // 失败状态
  const reject = reason => {
    if (this.status == 'pending') {
      this.status = 'rejected';
      this.reason = reason;
      this.onRejectedArray.forEach(func => func(reason));
    }
  };
  // 小优化:在执行executor时,也有可能代码报错,此时也应该进行reject
  try {
    executor(resolve, reject);
  } catch (err) {
    reject(err);
  }
}

LPromise.prototype.then = function (onFulfilled, onRejected) {
  let p2 = new Promise((resolve, reject) => {
    // 新建一个Promise,并且返回
    onFulfilled =
      typeof onFulfilled === 'function'
        ? onFulfilled
        : data => {
            return data;
          };
    onRejected =
      typeof onRejected === 'function'
        ? onRejected
        : data => {
            return data;
          };
    if (this.status == 'fulfilled') {
      // onFulfilled 成功的 .then 回调
      // 设置定时器的原因是在上面过程中,p2还没创建完毕,故需要开启定时器,等p2创建完成才能传值过去
      setTimeout(() => {
        let x = onFulfilled(this.value);
        resolvePromise(x, resolve, reject, p2); //将p2传进去
      }, 0);
    }
    if (this.status == 'rejected') {
      onRejected(this.reason);
    }
    if (this.status == 'pending') {
      this.onFulfilledArray.push(onFulfilled);
      this.onRejectedArray.push(onRejected);
    }
  });
  return p2;
};

function resolvePromise(x, resolve, reject, p2) {
  // 将x和p2对比,如果相同则抛出异常
  if (x === p2) {
    return reject(
      new TypeError(
        'Uncaught (in promise) TypeError: Chaining cycle detected for promise #<Promise>'
      )
    );
  }
  if (x instanceof Promise) {
    x.then(
      value => {
        resolve(value);
      },
      err => {
        reject(err);
      }
    );
  } else {
    resolve(x);
  }
}
const p1 = new LPromise((resolve, reject) => {
  resolve('1');
});

const p2 = p1.then(res => {
  console.log('res===', res);
  return p2;
});

完整代码

function LPromise(executor) {
  this.status = 'pending';
  this.value = null;
  this.reason = null;
  this.onFulfilledArray = []; // 成功回调数组
  this.onRejectedArray = []; // 失败回调数组
  // 成功状态
  const resolve = value => {
    if (this.status == 'pending') {
      this.status = 'fulfilled';
      this.value = value;
      this.onFulfilledArray.forEach(func => func(value));
    }
  };
  // 失败状态
  const reject = reason => {
    if (this.status == 'pending') {
      this.status = 'rejected';
      this.reason = reason;
      this.onRejectedArray.forEach(func => func(reason));
    }
  };
  // 小优化:在执行executor时,也有可能代码报错,此时也应该进行reject
  try {
    executor(resolve, reject);
  } catch (err) {
    reject(err);
  }
}

LPromise.prototype.then = function (onFulfilled, onRejected) {
  // 新建一个Promise,并且返回
  onFulfilled =
    typeof onFulfilled === 'function'
      ? onFulfilled
      : data => {
          return data;
        };
  onRejected =
    typeof onRejected === 'function'
      ? onRejected
      : data => {
          return data;
        };
  let p2 = new Promise((resolve, reject) => {
    if (this.status == 'fulfilled') {
      // onFulfilled 成功的 .then 回调
      // 设置定时器的原因是在上面过程中,p2还没创建完毕,故需要开启定时器,等p2创建完成才能传值过去
      setTimeout(() => {
        try {
          let x = onFulfilled(this.value);
          resolvePromise(p2, x, resolve, reject);
        } catch (err) {
          reject(err);
        }
      }, 0);
    }
    if (this.status == 'rejected') {
      try {
        let x = onRejected(this.reason);
        resolvePromise(p2, x, resolve, reject);
      } catch (err) {
        reject(err);
      }
    }
    if (this.status == 'pending') {
      this.onFulfilledArray.push(() => {
        // 异步
        setTimeout(() => {
          try {
            let x = onFulfilled(this.value);
            resolvePromise(p2, x, resolve, reject);
          } catch (err) {
            reject(err);
          }
        }, 0);
      });
      this.onRejectedArray.push(() => {
        // 异步
        setTimeout(() => {
          try {
            let x = onRejected(this.reason);
            resolvePromise(p2, x, resolve, reject);
          } catch (err) {
            reject(err);
          }
        }, 0);
      });
    }
  });
  return p2;
};

function resolvePromise(promise2, x, resolve, reject) {
  // 循环引用报错
  if (x === promise2) {
    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方法
      let then = x.then;
      // 如果then是函数,就默认是promise了
      if (typeof then === 'function') {
        // 就让then执行 第一个参数是this   后面是成功的回调 和失败的回调
        then.call(
          x,
          y => {
            // 成功和失败只能调用一个
            if (called) return;
            called = true;
            // resolve的结果依旧是promise 那就继续解析
            resolvePromise(promise2, y, resolve, reject);
          },
          err => {
            // 成功和失败只能调用一个
            if (called) return;
            called = true;
            reject(err); // 失败就失败了
          }
        );
      } else {
        resolve(x); //直接成功即可
      }
    } catch (err) {
      // 也属于失败
      if (called) return;
      called = true;
      // 取出then出错了就不要继续执行
      reject(err);
    }
  } else {
    resolve(x);
  }
}

const p = new LPromise((resolve, reject) => {
  resolve('hello');
});

p.then(res => {
  console.log(res, '1111');
  return new Promise((resolve, reject) => {
    resolve(22222);
  });
})
  .then(res => {
    console.log(res, '2222');
    return 3;
  })
  .then(res => {
    console.log(res, '3333');
  });

如何实现 resolvePromise(非重点)

resolvePromise(promise2, x, resolve, reject);

resolvePromise 的规范

  • 如果 promise2 和 x 相等,那么 reject error;
  • 如果 x 是一个 promise
    • 如果 x 是一个 pending 状态,那么 promise 必须要在 pending, 直到 x 变成 fulfilled or rejected
    • 如果 x 被 fulfilled, fulfill promise with the same value
    • 如果 x 被 rejected, reject promise with the same reason
  • 如果 x 是一个 object 或者 function
    • let thenable = x.then

    • 如果 x.then 这一步出错,那么 reject promise with e as the reason

    • 如果 then 是一个函数,then.call(x, resolvePromiseFn, rejectPromise)

resolvePromiseFn 的 入参是 y, 执行 resolvePromise(promise2, y, resolve, reject);

rejectPromise 的 入参是 r, reject promise with r.

如果 resolvePromise 和 rejectPromise 都调用了,那么第一个调用优先,后面的调用忽略。

如果调用then抛出异常e

如果 resolvePromise 或 rejectPromise 已经被调用,那么忽略

则,reject promise with e as the reason

如果 then 不是一个function. fulfill promise with x.

三、面试常考

1、如何并发执行Promise

const promiseArrGenerator = (num) => new Array(num).fill(0).map((item, index) => () => new Promise((resolve, reject) => {
    setTimeout(() => resolve(index), Math.random() * 10)
}));

const proArr = promiseArrGenerator(100);

Promise.all(proArr.map(fn => fn())).then(res => console.log(res))

2、promiseChain 顺序执行这些 promise

// proArr.forEach(fn => fn().then(res => console.log(res)));  
// 执行顺序是乱的

// 按顺序执行
const promiseArrGenerator = num =>
  new Array(num).fill(0).map(
    (item, index) => () =>
      new Promise((resolve, reject) => {
        setTimeout(() => resolve(index), Math.random() * 10);
      })
  );

const proArr = promiseArrGenerator(10);


const promiseChain = proArr => {
  proArr
    .reduce(
      (prochain, pro) =>
        prochain.then(res => {
          // 初始化-1不打印
          ~res && console.log(res, 111);
          return pro();
        }),
      Promise.resolve(-1)
    )
    .then(res => console.log(`the last one is ${res}`));
};

promiseChain(proArr);

3、设计一个 pipe, 我去并发执行一部分。

// 例子:chrome 6 个并发。100 接口。

// 3. 设计一个 pipe, 我去并发执行一部分。
// chrome 6 个并发。100 接口。
const promiseArrGenerator = num =>
  new Array(num).fill(0).map(
    (item, index) => () =>
      new Promise((resolve, reject) => {
        setTimeout(() => resolve(index), Math.random() * 5000);
      })
  );

const proArr = promiseArrGenerator(100);

const promisePipe = (proArr, concurrent) => {
  if (concurrent > proArr.length) {
    return Promise.all(proArr.map(fn => fn())).then(res => console.log(res));
  }

  let _arr = [...proArr];
  // 100 个 函数, 并发 ------ 保持当前请求一直有6个
  for (let i = 0; i < concurrent; i++) {
    let fn = _arr.shift();
    run(fn);
  }

  function run(fn) {
    fn().then(res => {
      console.log(res);
      if (_arr.length) {
        run(_arr.shift());
      }
    });
  }
};

promisePipe(proArr, 6);

参考

promise规范及应用