写在后面

Javascript异步编程先后经验了四个阶段,别离是Callback阶段,Promise阶段,Generator阶段和Async/Await阶段。Callback很快就被发现存在回调天堂和控制权问题,Promise就是在这个工夫呈现,用以解决这些问题,Promise并非一个新事务,而是依照一个标准实现的类,这个标准有很多,如 Promise/APromise/BPromise/D 以及 Promise/A 的升级版 Promise/A+,最终 ES6 中采纳了 Promise/A+ 标准。起初呈现的Generator函数以及Async函数也是以Promise为根底的进一步封装,可见Promise在异步编程中的重要性。

对于Promise的材料曾经很多,但每个人了解都不一样,不同的思路也会有不一样的播种。这篇文章会着重写一下Promise的实现以及笔者在日常应用过程中的一些心得体会。

实现Promise

标准解读

Promise/A+标准次要分为术语、要求和注意事项三个局部,咱们重点看一下第二局部也就是要求局部,以笔者的了解大略阐明一下,具体细节参照完整版Promise/A+规范。

1、Promise有三种状态pendingfulfilledrejected。(为了一致性,此文章称fulfilled状态为resolved状态)

  • 状态转换只能是pendingresolved或者pendingrejected
  • 状态一旦转换实现,不能再次转换。

2、Promise领有一个then办法,用以解决resolvedrejected状态下的值。

  • then办法接管两个参数onFulfilledonRejected,这两个参数变量类型是函数,如果不是函数将会被疏忽,并且这两个参数都是可选的。
  • then办法必须返回一个新的promise,记作promise2,这也就保障了then办法能够在同一个promise上屡次调用。(ps:标准只要求返回promise,并没有明确要求返回一个新的promise,这里为了跟ES6实现保持一致,咱们也返回一个新promise
  • onResolved/onRejected有返回值则把返回值定义为x,并执行[[Resolve]](promise2, x);
  • onResolved/onRejected运行出错,则把promise2设置为rejected状态;
  • onResolved/onRejected不是函数,则须要把promise1的状态传递上来。

3、不同的promise实现能够的交互。

  • 标准中称这一步操作为promise解决过程,函数标示为[[Resolve]](promise, x),promise为要返回的新promise对象,xonResolved/onRejected的返回值。如果xthen办法且看上去像一个promise,咱们就把x当成一个promise的对象,即thenable对象,这种状况下尝试让promise接管x的状态。如果x不是thenable对象,就用x的值来执行 promise
  • [[Resolve]](promise, x)函数具体运行规定:

    • 如果 promisex 指向同一对象,以 TypeError 为据因拒绝执行 promise;
    • 如果 xPromise ,则使 promise 承受 x 的状态;
    • 如果 x 为对象或者函数,取x.then的值,如果取值时呈现谬误,则让promise进入rejected状态,如果then不是函数,阐明x不是thenable对象,间接以x的值resolve,如果then存在并且为函数,则把x作为then函数的作用域this调用,then办法接管两个参数,resolvePromiserejectPromise,如果resolvePromise被执行,则以resolvePromise的参数value作为x持续调用[[Resolve]](promise, value),直到x不是对象或者函数,如果rejectPromise被执行则让promise进入rejected状态;
    • 如果 x 不是对象或者函数,间接就用x的值来执行promise

代码实现

标准解读第1条,代码实现:

class Promise {  // 定义Promise状态,初始值为pending  status = 'pending';  // 状态转换时携带的值,因为在then办法中须要解决Promise胜利或失败时的值,所以须要一个全局变量存储这个值  data = '';  // Promise构造函数,传入参数为一个可执行的函数  constructor(executor) {    // resolve函数负责把状态转换为resolved    function resolve(value) {      this.status = 'resolved';      this.data = value;    }    // reject函数负责把状态转换为rejected    function reject(reason) {      this.status = 'rejected';      this.data = reason;    }    // 间接执行executor函数,参数为处理函数resolve, reject。因为executor执行过程有可能会出错,谬误状况须要执行reject    try {      executor(resolve, reject);    } catch(e) {      reject(e)    }  }}

标准解读第2条,代码实现:

  /**    * 领有一个then办法    * then办法提供:状态为resolved时的回调函数onResolved,状态为rejected时的回调函数onRejected    * 返回一个新的Promise  */  then(onResolved, onRejected) {    // 设置then的默认参数,默认参数实现Promise的值的穿透    onResolved = typeof onResolved === 'function' ? onResolved : function(v) { return e };    onRejected = typeof onRejected === 'function' ? onRejected : function(e) { throw e };        let promise2;        promise2 =  new Promise((resolve, reject) => {      // 如果状态为resolved,则执行onResolved      if (this.status === 'resolved') {        try {          // onResolved/onRejected有返回值则把返回值定义为x          const x = onResolved(this.data);          // 执行[[Resolve]](promise2, x)          resolvePromise(promise2, x, resolve, reject);        } catch (e) {          reject(e);        }      }      // 如果状态为rejected,则执行onRejected      if (this.status === 'rejected') {        try {          const x = onRejected(this.data);          resolvePromise(promise2, x, resolve, reject);        } catch (e) {          reject(e);        }      }    });        return promise2;  }

当初咱们就依照标准解读第2条,实现了上述代码,上述代码很显著是有问题的,问题如下

  1. resolvePromise未定义;
  2. then办法执行的时候,promise可能依然处于pending状态,因为executor中可能存在异步操作(理论状况大部分为异步操作),这样就导致onResolved/onRejected失去了执行机会;
  3. onResolved/onRejected这两相函数须要异步调用(官网Promise实现的回调函数总是异步调用的)。

解决办法:

  1. 依据标准解读第3条,定义并实现resolvePromise函数;
  2. then办法执行时如果promise依然处于pending状态,则把处理函数进行贮存,等resolve/reject函数真正执行的的时候再调用。
  3. promise.then属于微工作,这里咱们为了不便,用宏工作setTiemout来代替实现异步,具体细节特地举荐这篇文章。

好了,有了解决办法,咱们就把代码进一步欠缺:

class Promise {  // 定义Promise状态变量,初始值为pending  status = 'pending';  // 因为在then办法中须要解决Promise胜利或失败时的值,所以须要一个全局变量存储这个值  data = '';  // Promise resolve时的回调函数集  onResolvedCallback = [];  // Promise reject时的回调函数集  onRejectedCallback = [];  // Promise构造函数,传入参数为一个可执行的函数  constructor(executor) {    // resolve函数负责把状态转换为resolved    function resolve(value) {      this.status = 'resolved';      this.data = value;      for (const func of this.onResolvedCallback) {        func(this.data);      }    }    // reject函数负责把状态转换为rejected    function reject(reason) {      this.status = 'rejected';      this.data = reason;      for (const func of this.onRejectedCallback) {        func(this.data);      }    }    // 间接执行executor函数,参数为处理函数resolve, reject。因为executor执行过程有可能会出错,谬误状况须要执行reject    try {      executor(resolve, reject);    } catch(e) {      reject(e)    }  }  /**    * 领有一个then办法    * then办法提供:状态为resolved时的回调函数onResolved,状态为rejected时的回调函数onRejected    * 返回一个新的Promise  */  then(onResolved, onRejected) {    // 设置then的默认参数,默认参数实现Promise的值的穿透    onResolved = typeof onResolved === 'function' ? onResolved : function(v) { return e };    onRejected = typeof onRejected === 'function' ? onRejected : function(e) { throw e };    let promise2;    promise2 =  new Promise((resolve, reject) => {      // 如果状态为resolved,则执行onResolved      if (this.status === 'resolved') {        setTimeout(() => {          try {            // onResolved/onRejected有返回值则把返回值定义为x            const x = onResolved(this.data);            // 执行[[Resolve]](promise2, x)            this.resolvePromise(promise2, x, resolve, reject);          } catch (e) {            reject(e);          }        }, 0);      }      // 如果状态为rejected,则执行onRejected      if (this.status === 'rejected') {        setTimeout(() => {          try {            const x = onRejected(this.data);            this.resolvePromise(promise2, x, resolve, reject);          } catch (e) {            reject(e);          }        }, 0);      }      // 如果状态为pending,则把处理函数进行存储      if (this.status = 'pending') {        this.onResolvedCallback.push(() => {          setTimeout(() => {            try {              const x = onResolved(this.data);              this.resolvePromise(promise2, x, resolve, reject);            } catch (e) {              reject(e);            }          }, 0);        });        this.onRejectedCallback.push(() => {          setTimeout(() => {            try {              const x = onRejected(this.data);              this.resolvePromise(promise2, x, resolve, reject);            } catch (e) {              reject(e);            }          }, 0);        });      }    });    return promise2;  }  // [[Resolve]](promise2, x)函数  resolvePromise(promise2, x, resolve, reject) {      }  }

至此,标准中对于then的局部就全副实现结束了。

标准解读第3条,代码实现:

// [[Resolve]](promise2, x)函数  resolvePromise(promise2, x, resolve, reject) {    let called = false;    if (promise2 === x) {      return reject(new TypeError('Chaining cycle detected for promise!'))    }        // 如果x依然为Promise的状况    if (x instanceof Promise) {      // 如果x的状态还没有确定,那么它是有可能被一个thenable决定最终状态和值,所以须要持续调用resolvePromise      if (x.status === 'pending') {        x.then(function(value) {          resolvePromise(promise2, value, resolve, reject)        }, reject)      } else {         // 如果x状态曾经确定了,间接取它的状态        x.then(resolve, reject)      }      return    }      if (x !== null && (Object.prototype.toString(x) === '[object Object]' || Object.prototype.toString(x) === '[object Function]')) {      try {        // 因为x.then有可能是一个getter,这种状况下屡次读取就有可能产生副作用,所以通过变量called进行管制        const then = x.then         // then是函数,那就阐明x是thenable,继续执行resolvePromise函数,直到x为一般值        if (typeof then === 'function') {           then.call(x, (y) => {             if (called) return;            called = true;            this.resolvePromise(promise2, y, resolve, reject);          }, (r) => {            if (called) return;            called = true;            reject(r);          })        } else { // 如果then不是函数,那就阐明x不是thenable,间接resolve x          if (called) return ;          called = true;          resolve(x);        }      } catch (e) {        if (called) return;        called = true;        reject(e);      }    } else {      resolve(x);    }  }

这一步骤非常简单,只有依照标准转换成代码即可。

最初,残缺的Promise依照标准就实现结束了,是的,标准里并没有规定catchPromise.resolvePromise.rejectPromise.all等办法,接下来,咱们就看一看Promise的这些罕用办法。

Promise其余办法实现

1、catch办法

catch办法是对then办法的封装,只用于接管reject(reason)中的错误信息。因为在then办法中onRejected参数是可不传的,不传的状况下,错误信息会顺次往后传递,直到有onRejected函数接管为止,因而在写promise链式调用的时候,then办法不传onRejected函数,只须要在最开端加一个catch()就能够了,这样在该链条中的promise产生的谬误都会被最初的catch捕捉到。

  catch(onRejected) {    return this.then(null, onRejected);  }
2、done办法

catchpromise链式调用的开端调用,用于捕捉链条中的错误信息,然而catch办法外部也可能呈现谬误,所以有些promise实现中减少了一个办法donedone相当于提供了一个不会出错的catch办法,并且不再返回一个promise,个别用来完结一个promise链。

  done() {    this.catch(reason => {      console.log('done', reason);      throw reason;    });  }
3、finally办法

finally办法用于无论是resolve还是rejectfinally的参数函数都会被执行。

  finally(fn) {    return this.then(value => {      fn();      return value;    }, reason => {      fn();      throw reason;    });  };
4、Promise.all办法

Promise.all办法接管一个promise数组,返回一个新promise2,并发执行数组中的全副promise,所有promise状态都为resolved时,promise2状态为resolved并返回全副promise后果,后果程序和promise数组程序统一。如果有一个promiserejected状态,则整个promise2进入rejected状态。

  static all(promiseList) {    return new Promise((resolve, reject) => {      const result = [];      let i = 0;      for (const p of promiseList) {        p.then(value => {          result[i] = value;          if (result.length === promiseList.length) {            resolve(result);          }        }, reject);        i++;      }    });  }
5、Promise.race办法

Promise.race办法接管一个promise数组, 返回一个新promise2,程序执行数组中的promise,有一个promise状态确定,promise2状态即确定,并且同这个promise的状态统一。

  static race(promiseList) {    return new Promise((resolve, reject) => {      for (const p of promiseList) {        p.then((value) => {          resolve(value);           }, reject);      }    });  }
6、Promise.resolve办法/Promise.reject

Promise.resolve用来生成一个rejected实现态的promisePromise.reject用来生成一个rejected失败态的promise

  static resolve(value) {    let promise;    promise = new Promise((resolve, reject) => {      this.resolvePromise(promise, value, resolve, reject);    });      return promise;  }    static reject(reason) {    return new Promise((resolve, reject) => {      reject(reason);    });  }

罕用的办法根本就这些,Promise还有很多扩大办法,这里就不一一展现,基本上都是对then办法的进一步封装,只有你的then办法没有问题,其余办法就都能够依赖then办法实现。

Promise面试相干

面试相干问题,笔者只说一下我司这几年的状况,并不能代表全副状况,参考即可。
Promise是我司前端开发职位,nodejs开发职位,全栈开发职位,必问的一个知识点,次要问题会散布在Promise介绍、根底应用办法以及深层次的了解三个方面,问题个别在3-5个,依据面试者答复状况会适当增减。

1、简略介绍下Promise。

Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更正当和更弱小。它由社区最早提出和实现,ES6 将其写进了语言规范,对立了用法,原生提供了Promise对象。有了Promise对象,就能够将异步操作以同步操作的流程表达出来,防止了层层嵌套的回调函数。此外,Promise对象提供对立的接口,使得管制异步操作更加容易。
(当然了也能够简略介绍promise状态,有什么办法,callback存在什么问题等等,这个问题是比拟凋谢的)

  • 发问概率:99%
  • 评分标准:人性化判断即可,此问题个别作为引入问题。
  • 加分项:纯熟说出Promise具体解决了那些问题,存在什么毛病,利用方向等等。
2、实现一个简略的,反对异步链式调用的Promise类。

这个答案不是固定的,能够参考最简实现 Promise,反对异步链式调用

  • 发问概率:50%(手撸代码题,因为这类题目比拟消耗工夫,一场面试并不会呈现很多,所以呈现频率不是很高,但却是必备常识)
  • 加分项:基本功能实现的根底上有onResolved/onRejected函数异步调用,谬误捕捉正当等亮点。
3、Promise.then在Event Loop中的执行程序。(能够间接问,也能够出具体题目让面试者答复打印程序)

JS中分为两种工作类型:macrotaskmicrotask,其中macrotask蕴含:主代码块,setTimeoutsetIntervalsetImmediate等(setImmediate规定:在下一次Event Loop(宏工作)时触发);microtask蕴含:Promiseprocess.nextTick等(在node环境下,process.nextTick的优先级高于Promise
Event Loop中执行一个macrotask工作(栈中没有就从事件队列中获取)执行过程中如果遇到microtask工作,就将它增加到微工作的工作队列中,macrotask工作执行结束后,立刻执行以后微工作队列中的所有microtask工作(顺次执行),而后开始下一个macrotask工作(从事件队列中获取)
浏览器运行机制可参考这篇文章

  • 发问概率:75%(能够了解为4次面试中3次会问到,顺便能够考查面试者对JS运行机制的了解)
  • 加分项:扩大讲述浏览器运行机制。
4、论述Promise的一些静态方法。

Promise.deferredPromise.allPromise.racePromise.resolvePromise.reject

  • 发问概率:25%(绝对根底的问题,个别在其余问题答复不是很现实的状况下发问,或者为了引出下一个题目而发问)
  • 加分项:越多越好
5、Promise存在哪些毛病。

1、无奈勾销Promise,一旦新建它就会立刻执行,无奈中途勾销。
2、如果不设置回调函数,Promise外部抛出的谬误,不会反馈到内部。
3、吞掉谬误或异样,谬误只能程序解决,即使在Promise链最初增加catch办法,仍然可能存在无奈捕获的谬误(catch外部可能会呈现谬误)
4、浏览代码不是一眼能够看懂,你只会看到一堆then,必须本人在then的回调函数外面理清逻辑。

  • 发问概率:25%(此问题作为进步题目,呈现概率不高)
  • 加分项:越多越正当越好(网上有很多说法,不一一佐证)

(此题目,欢送大家补充答案)

6、应用Promise进行程序(sequence)解决。

1、应用async函数配合await或者应用generator函数配合yield
2、应用promise.then通过for循环或者Array.prototype.reduce实现。

function sequenceTasks(tasks) {    function recordValue(results, value) {        results.push(value);        return results;    }    var pushValue = recordValue.bind(null, []);    return tasks.reduce(function (promise, task) {        return promise.then(() => task).then(pushValue);    }, Promise.resolve());}
  • 发问概率:90%(我司发问概率极高的题目,即能考查面试者对promise的了解水平,又能考查编程逻辑,最初还有bindreduce等办法的使用)
  • 评分标准:说出任意解决办法即可,其中只能说出async函数和generator函数的能够失去20%的分数,能够用promise.then配合for循环解决的能够失去60%的分数,配合Array.prototype.reduce实现的能够失去最初的20%分数。
7、如何进行一个Promise链?

在要进行的promise链地位增加一个办法,返回一个永远不执行resolve或者rejectPromise,那么这个promise永远处于pending状态,所以永远也不会向下执行thencatch了。这样咱们就进行了一个promise链。

    Promise.cancel = Promise.stop = function() {      return new Promise(function(){})    }
  • 发问概率:50%(此问题次要考查面试者罗辑思维)

(此题目,欢送大家补充答案)

8、Promise链上返回的最初一个Promise出错了怎么办?

catchpromise链式调用的开端调用,用于捕捉链条中的错误信息,然而catch办法外部也可能呈现谬误,所以有些promise实现中减少了一个办法donedone相当于提供了一个不会出错的catch办法,并且不再返回一个promise,个别用来完结一个promise链。

  done() {    this.catch(reason => {      console.log('done', reason);      throw reason;    });  }
  • 发问概率:90%(同样作为出题率极高的一个题目,充沛考查面试者对promise的了解水平)
  • 加分项:给出具体的done()办法代码实现
9、Promise存在哪些应用技巧或者最佳实际?

1、链式promise要返回一个promise,而不只是结构一个promise
2、正当的应用Promise.allPromise.race等办法。
3、在写promise链式调用的时候,then办法不传onRejected函数,只须要在最开端加一个catch()就能够了,这样在该链条中的promise产生的谬误都会被最初的catch捕捉到。如果catch()代码有呈现谬误的可能,须要在链式调用的开端减少done()函数。

  • 发问概率:10%(出题概率极低的一个题目)
  • 加分项:越多越好

(此题目,欢送大家补充答案)

至此,我司对于Promise的一些面试题目就列举结束了,有些题目的答案是凋谢的,欢送大家一起补充欠缺。总结起来,Promise作为js面试必问局部还是绝对容易把握并通过的。

总结

Promise作为所有js开发者的必备技能,其实现思路值得所有人学习,通过这篇文章,心愿小伙伴们在当前编码过程中能更加纯熟、更加明确的应用Promise。

参考链接:

http://liubin.org/promises-book
https://github.com/xieranmaya/blog/issues/3
https://segmentfault.com/a/1190000016550260