Promise总结

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

内容简介:作为一个前端开发,使用了Promise一年多了,一直以来都停留在API的调用阶段,没有很好的去深入。刚好最近阅读了V8团队的一篇Promise是社区中对于异步的一种解决方案,相对于回调函数和事件机制更直观和容易理解。ES6 将其写进了语言标准,统一了用法,提供了原生的Promise对象。

更好的阅度体验

  • 前言
  • API
  • Promise特点
  • 状态跟随
  • V8中的async await和Promise
  • 实现一个Promise
  • 参考

前言

作为一个前端开发,使用了Promise一年多了,一直以来都停留在API的调用阶段,没有很好的去深入。刚好最近阅读了V8团队的一篇 如何实现更快的async await ,借着这个机会整理了Promise的相关理解。文中如有错误,请轻喷~

API

Promise是社区中对于异步的一种解决方案,相对于回调函数和事件机制更直观和容易理解。ES6 将其写进了语言标准,统一了用法,提供了原生的Promise对象。

这里只对API的一些特点做记录,如果需要详细教程,推荐阮老师的 Promise对象一文

new Promise--创建一个promise实例

Promise.prototype.then(resolve, reject)--then方法返回一个新的Promise实例

Promise.prototype.catch(error)

--.then(null, rejection)或.then(undefined, rejection)的别名,用于指定发生错误时的回调函数。

--错误会一直传递,直到被catch,如果没有catch,则没有任何反应

--catch返回一个新的Promise实例

Promise.prototype.finally()

--指定不管 Promise 对象最后状态如何,都会执行的操作。

--实现如下:

Promise.prototype.finally = function (callback) {
  let P = this.constructor;
  return this.then(
    value  => P.resolve(callback()).then(() => value),
    reason => P.resolve(callback()).then(() => { throw reason })
  );
};
Promise.all([promise Array])

--将多个 Promise 实例,包装成一个新的 Promise 实例

--所有子promise执行完成后,才执行all的resolve,参数为所有子promise返回的数组

--某个子promise出错时,执行all的reject,参数为第一个被reject的实例的返回值

--某个子promise自己catch时,不会传递reject给all,因为catch重新返回一个promise实例

Promise.race([promise Array])

--将多个 Promise 实例,包装成一个新的 Promise 实例。

--子promise有一个实例率先改变状态,race的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给race的回调函数。

Promise.resolve()

--将现有对象转为 Promise 对象

--参数是promise实例, 原封不动的返回

--参数是一个thenable对象 将这个对象转为 Promise 对象,状态为resolved

--参数是一个原始值 返回一个新的 Promise 对象,状态为resolved

--不带有任何参数 返回一个resolved状态的 Promise 对象。

--等价于如下代码

Promise.resolve('foo')
// 等价于
new Promise(resolve => resolve('foo'))
Promise.reject()

--返回一个新的 Promise 实例,该实例的状态为rejected

--Promise.reject()方法的参数,会原封不动地作为reject的理由,变成后续方法的参数。

Promise特点

很多文章都是把resolve当成fulfilled,本文也是,但本文还有另外一个resolved,指的是该Promise已经被处理,注意两者的区别
1. 对象具有三个状态,分别是pending(进行中)、fulfilled(resolve)(已成功)、reject(已失败),并且对象的状态不受外界改变,只能从pending到fulfilled或者pending到reject。  

2. 一旦状态被改变,就不会再变,任何时候都能得到这个结果,与事件回调不同,事件回调在事件过去后无法再调用函数。  

3. 一个promise一旦resolved,再次resolve/reject将失效。即只能resolved一次。  

4. 值穿透,传给then或者catch的参数为非函数时,会发生穿透(下面有示例代码)  

5. 无法取消,Promise一旦运行,无法取消。  

6. 如果不设置回调函数,Promise内部抛出的错误,不会反应到外部  

7. 处于pending时,无法感知promise的状态(刚刚开始还是即将完成)。

值穿透代码:

new Promise(resolve=>resolve(8))
  .then()
  .then()
  .then(function foo(value) {
    console.log(value)  // 8
  })

状态追随

状态追随的概念和下面的v8处理asyac await相关联

状态跟随就是指将一个promise(代指A)当成另外一个promise(代指B)的resolve参数,即B的状态会追随A。

如下代码所示:

const promiseA = new Promise((resolve) => {
  setTimeout(() => {
    resolve('ccc')
  }, 3000)
})
const promiseB = new Promise(res => {
  res(promiseA)
})
promiseB.then((arg) => {
  console.log(arg) // print 'ccc' after 3000ms
})

按理说,promiseB应该是已经处于resolve的状态, 但是依然要3000ms后才打印出我们想要的值, 这难免让人困惑

在ES的标准文档中有这么一句话可以帮助我们理解:

A resolved promise may be pending, fulfilled or rejected.

就是说一个已经处理的promise,他的状态有可能是pending, fulfilled 或者 rejected。 这与我们前面学的不一样啊, resolved了的promise不应该是处于结果状态吗?这确实有点反直觉,结合上面的例子看, 当处于状态跟随时,即使promiseB立即被resolved了,但是因为他追随了promiseA的状态,而A的状态则是pending,所以才说处于resolved的promiseB的状态是pending。

再看另外一个例子:

const promiseA = new Promise((resolve) => {
  setTimeout(() => {
    resolve('ccc')
  }, 3000)
})
const promiseB = new Promise(res => {
  setTimeout(() => {
    res(promiseA)
  }, 5000)
})
promiseB.then((arg) => {
  console.log(arg) // print 'ccc' after 5000ms
})

其实理解了上面的话,这一段的代码也比较容易理解,只是因为自己之前进了牛角尖,所以特意记录下来:

  1. 3s后 promiseA状态变成resolve
  2. 5s后 promiseB被resolved, 追随promiseA的状态
  3. 因为promiseA的状态为resolve, 所以打印 ccc

V8中的async await和Promise

在进入正题之前,我们可以先看下面这段代码:

const p = Promise.resolve();

(async () => {
  await p;
  console.log("after:await");
})();

p.then(() => {
  console.log("tick:a");
}).then(() => {
  console.log("tick:b");
});

V8团队的博客中, 说到这段代码的运行结果有两种:

Node8(错误的):

tick a  
tick b  
after: await

Node10(正确的):

after await  
tick a  
tick b

ok, 问题来了, 为啥是这个样子?

先从V8对于await的处理说起, 这里引用一张官方博客的图来说明Node8 await的伪代码:

Node8 await

Promise总结

对于上面的例子代码翻译过来就(该代码引用自V8是 怎么实现更快的async await )是:

const p = Promise.resolve();

(() => {
  const implicit_promise = new Promise(resolve => {
    const promise = new Promise(res => res(p));
    promise.then(() => {
      console.log("after:await");
      resolve();
    });
  });

  return implicit_promise;
})();

p.then(() => {
  console.log("tick:a");
}).then(() => {
  console.log("tick:b");
});

很明显,内部那一句 new Promise(res => res(p)); 代码就是一个状态跟随,即promise追随p的状态,那这跟上面的结果又有什么关系?

在继续深入之前, 我们还需要了解一些概念:

task和microtask

JavaScript 中有 task 和 microtask 的概念。 Task 处理 I/O 和计时器等事件,一次执行一个。 Microtask 为 async/await 和 promise 实现延迟执行,并在每个任务结束时执行。 总是等到 microtasks 队列被清空,事件循环执行才会返回。

如官方提供的一张图:

Promise总结

EnqueueJob、PromiseResolveThenableJob和PromiseReactionJob

EnquequeJob: 存放两种类型的任务, 即PromiseResolveThenableJob和PromiseReactionJob, 并且都是属于microtask类型的任务

PromiseReactionJob: 可以通俗的理解为promise中的回调函数

PromiseResolveThenableJob(promiseToResolve, thenable, then): 创建和 promiseToResolve 关联的 resolve function 和 reject function。以 then 为调用函数,thenable 为this,resolve function和reject function 为参数调用返回。(下面利用代码讲解)

状态跟随的内部

再以之前的代码为例子

const promiseA = new Promise((resolve) => {
  resolve('ccc')
})
const promiseB = new Promise(res => {
  res(promiseA)
})

当promiseB被resolved的时候, 也就是将一个promise(代指A)当成另外一个promise(代指B)的resolve参数,会向EnquequeJob插入一个PromiseResolveThenableJob任务,PromiseResolveThenableJob大概做了如下的事情:

() => { 
  promiseA.then(
    resolvePromiseB, 
    rejectPromiseB
  );
}

并且当resolvePromiseB执行后, promiseB的状态才变成resolve,也就是B追随A的状态

Node 8中的流程

1. p处于resolve状态,promise调用then被resolved,同时向microtask插入任务PromiseResolveThenableJob  
2. p.then被调用, 向microtask插入任务tickA  
3. 执行PromiseResolveThenableJob, 向microtask插入任务resolvePromise(如上面的promiseA.then(...))  
4. 执行tickA(即输出tick: a),返回一个promise, 向microtask插入任务tickB  
5. 因为microtask的任务还没执行完, 继续  
6. 执行resolvePromise, 此时promise终于变成resolve, 向microtask插入任务'after await'  
7. 执行tickB(即输出tick: b)  
8. 执行'after await'(即输出'after await')

Node 10 await

老规矩, 先补一张伪代码图:

Promise总结

翻译过来就是酱紫:

const p = Promise.resolve();

(() => {
  const implicit_promise = new Promise(resolve => {
    const promise = Promise.resolve(p)
    promise.then(() => {
      console.log("after:await");
      resolve();
    });
  });

  return implicit_promise;
})();

p.then(() => {
  console.log("tick:a");
}).then(() => {
  console.log("tick:b");
});

因为p是一个promise, 然后Promise. resolve会直接将P返回 ,也就是

p === promise // true

因为直接返回了p,所以省去了中间两个microtask任务,并且输出的顺序也变得正常,也就是V8所说的更快的async await

实现一个Promise

先实现一个基础的函数

function Promise(cb) {
  const that = this
  that.value = undefined // Promise的值
  that.status = 'pending' // Promise的状态
  that.resolveArray = [] // resolve函数集合
  that.rejectArray = []  // reject函数集合

  function resolve(value) {
    if (value instanceof Promise) {
      return value.then(resolve, reject)
    }
    setTimeout(function() {
      if (that.status === 'pending') { // 处于pending状态 循环调用
        that.value = value
        that.status = 'resolve'
        for(let i = 0; i < that.resolveArray.length; i++) {
          that.resolveArray[i](value)
        }
      }
    })
  }
  function reject(reason) {
    if (reason instanceof Promise) {
      return reason.then(resolve, reject)
    }
    setTimeout(function() {
      if (that.status === 'pending') { // 处于pending状态 循环调用
        that.value = reason
        that.status = 'reject'
        for(let i = 0; i < that.rejectArray.length; i++) {
          that.rejectArray[i](reason)
        }
      }
    })
  }

  try {
    cb(resolve, reject)
  } catch (e) {
    reject(e)
  }
}
Promise.prototype.then = function(onResolve, onReject) {
  var that = this
  var promise2 // 返回的Promise

  onResolve = typeof onResolve === 'function' ? onResolve : function(v) { return v }  //如果不是函数 则处理穿透值
  onReject = typeof onReject === 'function' ? onReject : function(v) { return v } //如果不是函数 则处理穿透值

  if (that.status === 'resolve') {
    return promise2 = new Promise(function(resolve, reject) {
      setTimeout(function() {
        try {
          const x = onResolve(that.value)
          if (x instanceof Promise) { // 如果onResolved的返回值是一个Promise对象,直接取它的结果做为promise2的结果
            x.then(resolve, reject)
          } else {
            resolve(x)
          }
        } catch (e) {
          reject(e)
        }
      })
    })
  }

  if (that.status === 'reject') {
    return promise2 = new Promise(function(resolve, reject) {
      setTimeout(function() {
        try {
          const x = onResolve(that.value)
          if (x instanceof Promise) { // 如果onResolved的返回值是一个Promise对象,直接取它的结果做为promise2的结果
            x.then(resolve, reject)
          } else {
            reject(x)
          }
        } catch (e) {
          reject(e)
        }
      })
    })
  }

  if (that.status === 'pending') {
    return promise2 = new Promise(function(resolve, reject) {
      that.resolveArray.push(function(value) {
        try {
          var x = onResolve(value)
          if (x instanceof Promise) {
            x.then(resolve, reject)
          }
        } catch (e) {
          reject(e)
        }
      })
      that.rejectArray.push(function(reason) {
        try {
          var x = onReject(reason)
          if (x instanceof Promise) {
            x.then(resolve, reject)
          }
        } catch (e) {
          reject(e)
        }
      })
    })
  }
}
Promise.prototype.catch = function(onReject) {
  return this.then(null, onReject)
}

参考

v8是怎么实现更快的 await ?深入理解 await 的运行机制 V8中更快的异步函数和promise 剖析Promise内部结构,一步一步实现一个完整的、能通过所有Test case的Promise类 PromiseA+ ES6入门 深入Promise


以上所述就是小编给大家介绍的《Promise总结》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

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

Rationality for Mortals

Rationality for Mortals

Gerd Gigerenzer / Oxford University Press, USA / 2008-05-02 / USD 65.00

Gerd Gigerenzer's influential work examines the rationality of individuals not from the perspective of logic or probability, but from the point of view of adaptation to the real world of human behavio......一起来看看 《Rationality for Mortals》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

随机密码生成器
随机密码生成器

多种字符组合密码

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试