Promise.then链式调用顺序

摘要:
您希望使用Promise异步实现递归调用接口,以生成简单的AI动作序列。返回一个已被拒绝的Promise,然后返回的Promise也将被拒绝,该Promise的拒绝状态下的回调函数的参数值将用作返回Promise的被拒绝状态下回调函数的值。我大体上理解了诺言的原则。Promise对象的状态为Pending,因为对象的相应then1函数体尚未执行[理论4]3.同步then2.如上所述,它依赖于then1返回的匿名Promise对象状态。

想用Promise异步实现一个递归调用的接口,用来做简单AI的动作序列。发现一开始接触这个then的时候,不是很清楚,参考了网上的一些写法,改成自己的有问题,所以先静下心来研究一下这个调用的顺序问题

例子

先看个例子,参考[1]

new Promise((resolve, reject) => {
  console.log("promise")
  resolve()
 })
    .then(() => {	// 执行.then的时候生成一个promise是给最后一个.then的
        console.log("then1")
        new Promise((resolve, reject) => {
            console.log("then1promise")
            resolve()
        })
  .then(() => {// 执行这个.then的时候,生成的promise是下面一个then的
   console.log("then1then1")
  })
  .then(() => {
   console.log("then1then2")
  })
    })
    .then(() => {
  // 这个
        console.log("then2")
    })

结果:

promise
then1
then1promise
then1then1
then2
then1then2

问题:主要是疑惑then2在then1then1和then1then2之间

理论

为了方便分析,列几个自己的理解,后面解释问题的时候方便。尤其是理论4和理论5

理论1:Promise是一个对象

Promise是一个对象,他包含了

  • 自己的函数体:new时候传进去的一个参数,是一个函数
  • 状态:fulfilled, pending, rejected
  • 异步函数队列:pending状态下放在这里的then回调函数体

理论2:resolve/reject是用来改变Promise对象的

  • resolve:pending改成fulfilled
  • reject:pending改成rejected

另外, resolve执行的时候会去检查Promise自己的队列,如果不是空的,会把回调函数体塞到nextTick队列中。 代码参考[4]

handlers.resolve = function (self, value) {
  var result = tryCatch(getThen, value);
  if (result.status === 'error') {
    return handlers.reject(self, result.value);
  }
  var thenable = result.value;

  if (thenable) {
    safelyResolveThenable(self, thenable);
  } else {
    self.state = FULFILLED;
    self.outcome = value;
    var i = -1;
    var len = self.queue.length;
    while (++i < len) {
      self.queue[i].callFulfilled(value);
    }
  }
  return self;
};

理论3:Promise的t’hen/catch方法执行后返回的也是一个Promise对象

参考[2],返回的Promise对象支持了链式调用

理论4:then函数返回的Promise对象什么时候resolve看返回值

参考[2]返回值一节

(1)返回了一个值,那么 then 返回的 Promise 将会成为接受状态,并且将返回的值作为接受状态的回调函数的参数值。
(2)没有返回任何值,那么 then 返回的 Promise 将会成为接受状态,并且该接受状态的回调函数的参数值为 undefined。
(3)抛出一个错误,那么 then 返回的 Promise 将会成为拒绝状态,并且将抛出的错误作为拒绝状态的回调函数的参数值。
(4)返回一个已经是接受状态的 Promise,那么 then 返回的 Promise 也会成为接受状态,并且将那个 Promise 的接受状态的回调函数的参数值作为该被返回的Promise的接受状态回调函数的参数值。
(5)返回一个已经是拒绝状态的 Promise,那么 then 返回的 Promise 也会成为拒绝状态,并且将那个 Promise 的拒绝状态的回调函数的参数值作为该被返回的Promise的拒绝状态回调函数的参数值。
(6)返回一个未定状态(pending)的 Promise,那么 then 返回 Promise 的状态也是未定的,并且它的终态与那个 Promise 的终态相同;同时,它变为终态时调用的回调函数参数与那个 Promise 变为终态时的回调函数的参数是相同的。

理论5:执行顺序为:同步执行 > nextTick队列 > setTimeout队列 > Promise对象私有队列

参考[3],文章写的不错。大体讲明白了Promise和then的原理了。因为文章里面的截图不是很清楚,我还是去github上面翻源码出来看的舒服点,参考[4]

Promise.prototype.then = function (onFulfilled, onRejected) {
  if (typeof onFulfilled !== 'function' && this.state === FULFILLED ||
    typeof onRejected !== 'function' && this.state === REJECTED) {
    return this;
  }
  var promise = new this.constructor(INTERNAL);
  if (this.state !== PENDING) {
    var resolver = this.state === FULFILLED ? onFulfilled : onRejected;
    unwrap(promise, resolver, this.outcome);
  } else {
    this.queue.push(new QueueItem(promise, onFulfilled, onRejected));
  }

  return promise;
};

简单说明:

执行then函数的时候,如果Promise状态是Fulfilled的话,就执行unwrap那个逻辑,把then函数体通过nextTick放到一个全局的队列
执行then函数的时候,如果Promise是Pending状态,就执行 this.queue.push ,看到this就知道这个是放在Promise对象自己的一个队列里面

分析时序

有了上面的这些理解,接下来分析一下一开始我不理解的打印结果。有些步骤简单明了,可能被我过了。当然,因为自己第一次去接触这个,所以步骤会写的比较繁琐一点~

  • 1.执行Promise的时候,函数体同步执行,直接resolve,所以Promise对象为fulfilled状态。[理论1,理论2]
  • 2.同步执行then1函数的时候,then本身是一个函数,只是它的参数(里面的函数体)没有执行。这个函数体放在nextTick队列中[理论5]。then1执行完的时候,同步返回一个匿名的Promise对象[理论3]。并且这个Promise对象的状态是Pending,因为这个对象相关的then1函数体还没有执行[理论4]
  • 3.同步执行then2。同上,它依赖then1返回的匿名Promise对象状态。因为是pending,那么then2函数体放在then1返回的匿名Promise对象自己的队列中
  • 4.下一个循环执行nextTick队列,也就是then1的函数体
  • 5.先同步执行一个new的Promise对象函数体,打印’then1 promise’.并且这个Promise对象的状态被resolve切换为fulfilled
  • 6.同步执行then1then1这个.then函数,并且把她的参数(回调函数体)放在nextTick队列中。并且返回的匿名Promise对象为pending状态
  • 7.同步执行then1then2这个.then函数,因为上面一个then1then1回调函数体还没有执行,返回一个pending状态的匿名Promise对象.所以这个then1then2函数回调函数体放在上面的匿名Promise对象的队列中
  • 8.最后,then1函数体没有返回任何东西,所以根据[理论4],这个Promise的状态变成了fulfilled,即第一个.then1函数返回的Promise对象的状态改变了。这个时候其实是执行了resolve函数切换状态,这个函数里面会看这个Promise对象自己的队列里面是不是有回调函数,然后把这些放到timeTick队列里面[理论3]。所以.then2的回调函数体从Promise对象自己的队列移到了nextTick队列里面。注意这个时候then1then1和then2的函数体都在tick队列中,但是都没有执行。再加上js是单线程,会顺序执行tick队列里面的回调函数体,所以接下来就按顺序执行then1then1和then2
  • 9.再下一个循环,分别执行then1then1和then2两回调函数体,所以会先打印‘then1then1’,然后是’then2’
  • 10.then1then1回调函数体执行完毕的时候,她的Promise对象也会切换为fulfilled,并且把then1then2回调函数体放在tick队列中
  • 11.再再下一个循环。最后执行then1then2函数体,打印’then1then2’。完美收官

资源搜索网站大全https://55wd.com广州品牌设计公司http://www.maiqicn.com

练习

如果例子中的new Promise改成,前面加一个return呢?

.then(() => {	// 执行.then的时候生成一个promise是给最后一个.then的
        console.log("then1")
        new Promise((resolve, reject) => {
            console.log("then1promise")
            resolve()
        })

// ===》改成
.then(() => {	// 执行.then的时候生成一个promise是给最后一个.then的
        console.log("then1")
        // 就是这里的加了一个return
        return new Promise((resolve, reject) => {
            console.log("then1promise")
            resolve()
        })

根据[理论4],.then1函数体有了返回值,是一个Promise对象,而且是then1then2执行完返回的匿名Promise对象。所以只有等这个Promise对象resolve了之后,才会执行then2回调函数体的逻辑,所以’then2’最后才会打印。所以最终结果是:

promise
then1
then1promise
then1then1
then1then2
then2a

免责声明:文章转载自《Promise.then链式调用顺序》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇Windows文件系统外显子分析:cutadapt,去除序列adapter详细解析下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

async与await总结

全手打原创,转载请标明出处:https://www.cnblogs.com/dreamsqin/p/11533174.html,多谢,=。=~  抛出3个疑问: 1、async是干哈的? 2、await在等啥? 3、await等到了又要干哈?  先说说AsyncFunction  AsyncFunction构造函数用来创建新的异步函数对象,JavaScri...

4、node中的宏任务和微任务(大活和小活)

执行流程: 宏任务和微任务(一个宏任务配多个微任务):每次执行微任务队列会全部执行完并清空 console.log('main') process.nextTick( () => { console.log('process.nextTick1') }) setTimeout(() => { console.log('setTim...

nodeJS从入门到进阶一(基础部分)

一、Node.js基础知识 1、概念 简单的说 Node.js 就是运行在服务端的 JavaScript。 Node.js 是JavaScript的运行环境 Node.js 使用了一个事件驱动、非阻塞式 I/O 的模型,使其轻量又高效。 2、安装使用 官网下载地址:http://nodejs.cn/ nodeJS5个基本对象: 1、require 引...

【Promise】Promise实现请求超时处理(加强版)

  昨天闲来无事,于是把之前写过的promise优化了一下,代码如下: /*写文件、追加写、读文件*/ var fs = require('fs'); function wrapper(fn,context) { var args = Array.prototype.slice.call(arguments,2); return...

ES6规范

一、简介 js由三部分组成:ES+DOM(文档对象模型)+ BOM(浏览器对象模型) ES作为核心,是一套标准,规范了语言的组成部分:语法、类型、语句、关键字、保留字。定义了数据结构和语法,定义了函数和对象的实现,包括原型链和作用域链的机制和实现。 JavaScript 的核心 ECMAScript 描述了该语言的语法和基本对象; DOM 描述了处理网页内...

Koa 框架整理

学习交流 Koa使用了ES6规范的generator和异步编程是一个更轻量级Web开发的框架,Koa 的先天优势在于 generator。由于是我个人的分享交流,所以Node基础、ES6标准、Web开发基础以及Koa的"Hello World"程序都不在讨论,希望各位小伙伴提出意见和指导。   PS:Koa 内核中没有捆绑任何中间件,但不用担心,Koa...