面试高频点-Promise解析和实现

栏目: JavaScript · 发布时间: 6年前

内容简介:Promise因为它的调用方式使得异步操作清晰简单,是现在异步操作的主要方式。Promise的使用和实现是面试中的高频问点。这篇文章主要解析Promise规范和一版实现方式。了解Promise首先我们要清楚Promise规范的内容,规范规定了Promise的行为和调用方式。这里是规范原文。下面是翻译总结:一个Promise主要代表了一个异步操作的最终结果。与Promise交互的主要方式是通过promise实例的

Promise因为它的调用方式使得异步操作清晰简单,是现在异步操作的主要方式。Promise的使用和实现是面试中的高频问点。这篇文章主要解析Promise规范和一版实现方式。

PromiseA+规范

了解Promise首先我们要清楚Promise规范的内容,规范规定了Promise的行为和调用方式。这里是规范原文。下面是翻译总结:

一个Promise主要代表了一个异步操作的最终结果。与Promise交互的主要方式是通过promise实例的 then 方法注册回调函数。回调函数分为两种,一种接受异步操作成功时的回调,接受异步操作结果值作为参数;第二种是异步操作失败时的回调,接受异步操作失败原因为参数。规范主要是详细描述了 then 方法的实现细节。

主要要求如下:

状态

一个promise必须处于这3种状态之中: pendingfullfilledrejected

  • pending : 可以转换到 fullfilled 或者 rejected 状态
  • fullfilled : 不能转换到任何状态且有一个不可变的结果值
  • rejected : 不能转换到任何状态且有一个不可变的失败原因

then 方法

一个 promise 必须有一个 then 方法来接受异步操作的结果值或者失败原因

一个 promise 的then应该接收两个参数,成功回调函数和失败回调函数:

promise.then(onFulfilled, onRejected)
复制代码
  • onFulfilled和onRejected都是可选的:

    • 如果onFulfilled不是一个函数,它必须被忽略
    • 如果onRejected不是一个函数,它必须被忽略
  • 如果onFulfilled是函数:

    • 它必须在 promise 状态变为 fullfilled 之后执行,且接受 promise 的结果值作为第一个参数
    • 它在 promise 状态变为 fullfilled 之前不能执行
    • 它最多被执行一次
  • 如果onRejected是函数:

    • 它必须在 promise 状态变为 rejected 之后执行,且接受 promise 的失败原因作为第一个参数
    • 它在 promise 状态变为 rejected 之前不能执行
    • 它最多被执行一次
  • onFulfilled 或者 onRejected 直到执行环境堆栈只包含平台代码时才可以执行

  • onFulfilled 或者 onRejected 必须作为一个独立的函数被调用。(不能作为对象属性执行或者其他指定this的执行方式,比如call和apply)

  • then 方法可以在同一个 promise 上多次调用:

    • promise 状态变为 fullfilled 时,通过then注册的所有 onFulfilled 回调按照注册顺序依次执行
    • promise 状态变为 rejected 时,通过then注册的所有 onRejected 回调按照注册顺序依次执行
  • then 方法必须返回一个promise:

    promise2 = promise1.then(onFulfilled, onRejected);
    复制代码
    • 如果 onFulfilled 或者 onRejected 返回 x , 则运行Pomise Resolution Procedure [[Resolve]](promise2, x)
    • 如果 onFulfilled 或者 onRejected 抛出一个异常 e , promise2 必须以 e 作为失败原因变成 rejected 状态
    • 如果 onFulfilled 不是一个函数,且 promise1fullfilled 状态,则 promise2 也转变为 fullfilled ,且结果值和 promise1 一样
    • 如果 onRejected 不是一个函数,且 promise1rejected 状态,则 promise2 也转变为 rejected ,且失败原因和 promise1 一样

The Promise Resolution Procedure

promise resolution procedure 表示为 [[Resolve]](promise, x) ,是一个接受一个promise和一个值作为参数的抽象操作。如果一个函数是一个 thenablethenable 表示有 then 方法的对象或者函数),则它试图使 promise 采用 x 的状态。这里对于 thenable 的处理使Promise更加通用,能够兼容之前并不符合规范但是有合理 then 方法的异步实现。

为了运行 [[Resolve]](promise, x) ,需要执行以下步骤:

  • 如果 promisex 指向同一个对象,则以 TypeError 为失败原因将 promise 转变为 rejected 状态
  • 如果 x 是一个promise,则 promise 采用 x 的状态以及相应的结果值或者原因
  • 如果 x 是一个对象或者函数
    • Let 定义 then ,值为 x.then
    • 如果在获取 x.then 的过程中抛出异常 e , promisee 作为失败原因变成 rejected 状态
    • 如果 then 是一个函数,以 x 作为它的 this 调用它,第一个参数为 resolvePromise ,第二个参数为 rejectPromise :
      • 如果 resolvePromise 以参数 y 被调用的话,则执行 [[Resolve]](promise, y)
      • 如果 rejectPromise 以参数 r 被调用的话,则以 r 作为失败原因使 promise 变成 rejected 状态
      • 如果 resolvePromiserejectPromise 都被调用了,或者以同样的参数被调用多次,则以第一次调用为准,忽略之后的调用
      • 如果调用 then 时抛出一个异常 e :
        • 如果 resolvePromiserejectPromise 被调用了,则忽略
        • 否则, promisee 作为失败原因变成 rejected 状态
    • 如果 then 不是一个函数,则 promisex 为结果值变为 fullfilled 状态
  • 如果 x 不是一个对象或者函数,则 promisex 为结果值变为 fullfilled 状态

Promise实现

我们可以看出标准还是挺复杂的,我们就一步一步,从简到繁地实现Promise。标准中并没有规定Promise如何创建,如何完成状态转换,这些我们可以参考ES6的promise用法:

const promise1 = new Promise((resolve, reject) => {

});
promise1.then((value) => {}, (reason)=>{})
复制代码

promise的状态

Promise以类的方式出现,构造函数接受一个函数 fn 作为参数, fn 有两个参数:

  • resolve ,使promise从 pending 状态转换为 fullfilled 状态, resolve(value) 的参数value为promise的结果值;
  • reject ,使promise从 pending 状态转换为 rejected 状态, reject(reason) 的参数reason为promise的失败原因。

我们第一版可以先从状态写起:

const PENDING = 'pending';
const FULLFILLED = 'fullfilled';
const REJECTED = 'rejected';
class Promise {
  constructor(fn) {
    this.status = PENDING;
    this.value = undefined;
    this.reason = undefined;
    const resolve = (value) => {
      if(this.status === PENDING){
        this.status = FULLFILLED;
        this.value = value;
      }
    }
    const reject = (reason) => {
      if(this.status === PENDING){
        this.status = REJECTED;
        this.reason = reason;
      }
    }
    try{
      fn(resolve, reject);
    }catch(e){
      reject(e);
    }
  }
}
复制代码

try...catch...是为了捕获用户自定义函数 fn 的错误,如果 fn 执行出错,则promise会进入 rejected 状态。

then函数的两个函数参数的执行

接下来我们考虑 then 函数, then 函数比较复杂,我们先考虑 then(onFulfilled, onRejected) 函数中两个函数参数的执行问题,把 then 函数的返回值问题放在后面。

then 函数中两个函数参数的执行情况分为3种:

  • promise处于 pending 状态,那在then函数中不执行两个函数参数,等到 fn 中的异步操作有结果了,再根据成功或者失败的结果去执行

  • promise处于 fullfilled 状态,则直接执行 then 函数的第一个函数参数 onFulfilled

  • promise处于 rejected 状态,则直接执行 then 函数的第二个函数参数 onRejected

class Promise {
  constructor(fn) {
    this.status = PENDING;
    this.value = undefined;
    this.reason = undefined;
    this.fullFilledCallbacks = []; // 存储状态转变之前的注册的onFulfilled
    this.rejectedCallbacks = []; // 存储状态转变之前的注册的onRejected
    const resolve = (value) => {
      if(this.status === PENDING){
        this.status = FULLFILLED;
        this.value = value;
        this.fullFilledCallbacks.forEach(callback => callback(value));
      }
    }
    const reject = (reason) => {
      if(this.status === PENDING){
        this.status = REJECTED;
        this.reason = reason;
        this.rejectedCallbacks.forEach(callback => callback(reason));
      }
    }
    try{
      fn(resolve, reject);
    }catch(e){
      reject(e);
    }
  }

  then(onFulfilled, onRejected){
    if(typeof onFullfilled !== 'function'){
      onFullfilled = () => {};
    }
    if(typeof onRejected !== 'function'){
      onRejected = () => {};
    }

    if (this.status === 'fullfilled'){
      onFulfilled(this.value);
    } else if (this.status === 'rejected'){
      onRejected(this.reason);
    } else {
      this.fullFilledCallbacks.push(onFulfilled);
      this.rejectedCallbacks.push(onRejected);
    }
  }
}
复制代码

以上代码很简单,我们借助 fullFilledCallbacksrejectedCallbacks 两个数组存储在promise状态为发生转变之前通过 then 方法注册的 onFulfilledonRejected 回调。

then的返回值

由PromiseA+规范可以, then 方法必须返回一个promise:

promise2 = promise1.then(onFulfilled, onRejected);
复制代码

且promise2的状态由 onFullfilled 或者 onRejected 的返回值决定,如何进行转换则由 promise resolution procedure 方法来实现。我们先不考虑 promise resolution procedure ,根据规范实现如下:

then(onFulfilled, onRejected){
    let _resolve;
    let _reject;
    const promise2 = new Promise((resolve, reject) => {
      _resolve = resolve;
      _reject = reject;
    });

    // 如果`onFulfilled`不是一个函数,且`promise1`为`fullfilled`状态,则`promise2`也转变为`fullfilled`,且结果值和`promise1`一样
    if(typeof onFullfilled !== 'function'){
      onFullfilled = (value) => value;
    }

    // 如果`onRejected`不是一个函数,且`promise1`为`rejected`状态,则`promise2`也转变为`rejected`,且失败原因和`promise1`一样
    if(typeof onRejected !== 'function'){
      onRejected = (reason) => throw new Error(reason);
    }

    // 如果`onFulfilled`或者`onRejected`返回`x`, 则运行Pomise Resolution Procedure`[[Resolve]](promise2, x)`
    const excuteFullfilled = () => {
      const x = onFulfilled(this.value);
      this.promiseResolution(promise2, x, _resolve, _reject);
    }
    const excuteRejected = () => {
      const x = onRejected(this.reason);
      this.promiseResolution(promise2, x, _resolve, _reject);
    }

    // 如果`onFulfilled`或者`onRejected`抛出一个异常`e`,`promise2`必须以`e`作为失败原因变成`rejected`状态
    try{
      if (this.status === 'fullfilled'){
        excuteFullfilled();
      } else if (this.status === 'rejected'){
        excuteRejected();
      } else {
        this.fullFilledCallbacks.push(() => {
          try{
           excuteFullfilled();
          }catch(e){
            _reject(e);
          }
        });
        this.rejectedCallbacks.push(() => {
          try{
            excuteRejected();
          }catch(e){
            _reject(e);
          }
        });
      }
    }catch(e){
      _reject(e);
    }

    return promise2;
  }

  promiseResolution(){
    ...
  }
复制代码

接下来我们来实现 promiseResolution 函数,如果已经忘了可以去复习一下 promiseResolution 的规范。 promiseResolution 的主要任务是根据 onFulFilled 或者 onRejected 的返回值 x 来决定 promise2 的状态转变。

promiseResolution(promise, x, resolve, reject){
  // 如果`resolvePromise`和`rejectPromise`都被调用了,或者以同样的参数被调用多次,则以第一次调用为准,忽略之后的调用
  let called = false;

  // 如果`resolvePromise`以参数`y`被调用的话,则执行`[[Resolve]](promise, y)`
  const resolvePromise = (y) => {
    if(!called){
      this.promiseResolution(promise, y, resolve, reject);
      called = true;
    }
  }

  // 如果`rejectPromise`以参数`r`被调用的话,则以`r`作为失败原因使``变成`rejected`状态
  const rejectPromise = (r) => {
    f(!called){
      reject(r);
      called = true;
    }
  }

  if(promise === x){
    throw new Error('TypeError');
  }

  // 如果`x`是一个promise,则`promise`采用`x`的状态以及相应的结果值或者原因
  if( x instanceof Promise){
    x.then(resovle, reject)
  }

  if( x && (typeof x === 'object' || typeof x === 'function')){
    let then = x.then;
    if(typeof then === 'function'){
      try{
        then.call(x, resolvePromise, rejectPromise);
      }catch(e){
        if(!called){
          reject(e);
        }
      }
    }else{
      // 如果`then`不是一个函数,则`promise`以`x`为结果值变为`fullfilled`状态
      resolve(x);
    }
  } else {
    // 如果`x`不是一个对象或者函数,则`promise`以`x`为结果值变为`fullfilled`状态
    resolve(x);
  }

}
复制代码

异步问题

规范中有一条时, onFulfilled 或者 onRejected 直到执行环境堆栈只包含平台代码时才可以执行。规范给出的注释是:平台代码是指引擎、执行环境和Promise实现代码,就是说js主栈中不能有其他代码,这就是要求我们异步执行 onFulfilled 或者 onRejected ;我们可以用宏任务(setTimeout 或者 setImmediate)或者微任务(MutationObserver or process.nextTick)机制来实现。

我们知道,ES6的promise的then方法回调异步执行的机制是微任务。在浏览器环境我们可以使用MutationObserver来模拟微任务机制,如果浏览器不支持 MutationObserver ,我们回退到setTimeout使用宏任务实现。

function isNative(fn){
  return fn.toString.includes('native code');
}

function asynTask(){
  if(typeof MutationObserver === 'function' && isNative(MutationObserver)){
    return (fn) => {
      var targetNode = document.createElement('div');
      var config = { attributes: true };
      // Create an observer instance linked to the callback function
      var observer = new MutationObserver(fn);
      // Start observing the target node for configured mutations
      observer.observe(targetNode, config);
      targetNode.id = 'anyway';
    }
  } else if(typeof setImmediate === 'function' && isNative(setImmediate)){
    return (fn) => {setImmediate(fn)};
  } else{
    return (fn) => {setTimeout(fn, 0)};
  }
}
class Promise{
  registerAsyn: asynExcute()
}
复制代码

我们使用异步机制来重写 then

then(onFulfilled, onRejected){
    let _resolve;
    let _reject;
    const promise2 = new Promise((resolve, reject) => {
      _resolve = resolve;
      _reject = reject;
    });

    // 如果`onFulfilled`不是一个函数,且`promise1`为`fullfilled`状态,则`promise2`也转变为`fullfilled`,且结果值和`promise1`一样
    if(typeof onFullfilled !== 'function'){
      onFullfilled = (value) => value;
    }

    // 如果`onRejected`不是一个函数,且`promise1`为`rejected`状态,则`promise2`也转变为`rejected`,且失败原因和`promise1`一样
    if(typeof onRejected !== 'function'){
      onRejected = (reason) => throw new Error(reason);
    }

    // 如果`onFulfilled`或者`onRejected`返回`x`, 则运行Pomise Resolution Procedure`[[Resolve]](promise2, x)`
    const excuteFullfilled = () => {
      try{
        const x = onFulfilled(this.value);
        this.promiseResolution(promise2, x, _resolve, _reject);
      }catch(e){
        _reject(e);
      }
    }
    const excuteRejected = () => {
      try{
        const x = onRejected(this.reason);
        this.promiseResolution(promise2, x, _resolve, _reject);
      }catch(e){
        _reject(e);
      }
    }

    if (this.status === 'fullfilled'){
      registerAysn(excuteFullfilled);
    } else if (this.status === 'rejected'){
      registerAysn(excuteRejected);
    } else {
      this.fullFilledCallbacks.push(() => {
        registerAysn(excuteFullfilled);
      });
      this.rejectedCallbacks.push(() => {
        registerAysn(excuteRejected);
      });
    }

    return promise2;
  }

复制代码

目前为止我们实现了Promise的基本功能。

Promise静态方法和其他实例方法

class Promise{
  catch(fn) {
    return this.then((null, fn);
  }

  finally(fn) {
    const P = this.constructor;
    return this.then(
      value  => P.resolve(fn()).then(() => value),
      reason => P.resolve(fn()).then(() => { throw reason })
    );
  }
}

//resolve方法

Promise.resolve = function(value) {
  if (value instanceof Promise) {
    return value;
  }

  return new Promise(function(resolve) {
    resolve(value);
  });
};

Promise.reject = function(value) {
  return new Promise(function(resolve, reject) {
    reject(value);
  });
};

Promise.race = function(promises) {
  return new Promise(function(resolve, reject) {
    for (var i = 0, len = promises.length; i < len; i++) {
      promises[i].then(resolve, reject);
    }
  });
};

Promise.all = function(promises){
  if (!promises || typeof promises.length === 'undefined')
      throw new TypeError('Promise.all should accepts an array');
  let values = [];
  let i = 0;
  let length = promises.length;
  function processData(index,data){
    values[index] = data;
    i++;
    if(i == 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解析和实现》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Google成功的七堂课

Google成功的七堂课

罗耀宗 / 电子工业出版社 / 2005-7 / 28.00元

Google是全球使用人数最多的搜索引擎,在短短几年内,Google从斯坦福大学的实验室,茁壮成长为举世瞩目的IT业超级巨人,他们的成功绝非偶然,尤其是在网络泡沫破灭,行业一片萧条之际,它的崛起更为IT业带来一缕曙光。作者从趋势观察家的角度,以讲座的形式,向读者讲述Google成功的关键因素:破除因循守旧、不断打破常规,核心技术领先、做出了“更好的捕鼠器”,使得Google在搜索技术方面远远超越对......一起来看看 《Google成功的七堂课》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换