promise,then,setTimeout -- 细致探讨执行流程

595 阅读5分钟

本文原理:

JavaScript是单线程,setTimeout会让代码置于线程末尾。

Promise建立后与正常代码一样顺序执行。

Promise在状态变为resolve后才会触发后续的then


  1. 读完本文大约需要15-25分钟
  2. 本文前置知识:基础js,Promise对象,ES6其他基础知识
  3. 阅读难度:初级
  4. 本文所有代码及输出结果都写了出来,可以不用编译器编译,直接浏览文章

如果你知道以下代码的执行结果,那么本文对你的收获就比较有限

const showNode = document.getElementById("show");

showNode.innerHTML += "normal | ";

let promise = new Promise((resolve, reject) => {
  showNode.innerHTML += "promise | ";
  setTimeout(() => { resolve("then |"); }, 0)
})

promise.then((res) => {
  setTimeout(() => { showNode.innerHTML += 'then wait | ' }, 0)
  showNode.innerHTML += res;
  return " next then | ";
}).then((res) => {
  showNode.innerHTML += res;
  return " next next then | ";
}).then((res) => {
  showNode.innerHTML += res;
})

setTimeout(() => { showNode.innerHTML += "wait | " }, 0)

// 输出: normal | promise | then | next then | next next then | wait | 
//       then wait |




正文开始

Javascript 引擎单线程机制

  • 首先明确,JavaScript引擎是单线程机制

  • JavaScript 是单线程执行的,无法同时执行多段代码。当某一段代码正在执行的时候,所有后续的任务都必须等待,形成一个任务队列。一旦当前任务执行完毕,再从队列中取出下一个任务,这也常被称为 “阻塞式执行”。

  • 可以理解为:只有在 JS线程中没有任何同步代码要执行的前提下才会执行异步代码

有关js线程的知识可以移步 JS 单线程与 setTimeout 执行原理


promise,then,setTimeout的执行流程

正常顺序执行 -> then -> setTimeouts -> then中 setTimeouts


注1: showNode 是我用于获取html的元素,直接显示在页面上,替换console.log

注2: 事例代码中,新加的代码或有修改的代码,会在代码后面加上相应的注释


正常执行 :按照正常从上往下的顺序执行

环境1 普通代码在promise之前

showNode.innerHTML += "normal | ";

let promise = new Promise((resolve, reject) => {
  showNode.innerHTML += "promise | ";
})

// 输出: normal | promise |

环境2 promise在普通代码之前

let promise = new Promise((resolve, reject) => {
  showNode.innerHTML += "promise | ";
})
  
showNode.innerHTML += "normal | ";

// 输出: promise | normal |
  • promise在建立后便立即执行,执行顺序按照正常代码执行顺序从上往下执行


加上then : 正常执行完成后,执行then

环境3 添加then

let promise = new Promise((resolve, reject) => {
  showNode.innerHTML += "promise | ";
  resolve("then |")                             // 新加代码
})

promise.then((res) => {                         // 新加代码
  showNode.innerHTML += res;                    // 新加代码
})                                              // 新加代码

showNode.innerHTML += "normal | ";              // 代码位置更改

// 输出: promise | normal | then |
  • 当promise的状态由 pending 变为 fulfilled 后,对应使用的 then 才会执行。
  • 正常执行的代码处在 执行第一批 ,then 则处在第二批,当第一批执行完成后再执行第二批


外部setTimeout : 在then之后执行

环境4 添加到全局里的setTimeout

setTimeout(() => { showNode.innerHTML += "wait | " }, 0)  // 新加代码

let promise = new Promise((resolve, reject) => {
  showNode.innerHTML += "promise | ";
  resolve("then |")
})

promise.then((res) => {
  showNode.innerHTML += res;
})

showNode.innerHTML += "normal | ";

// 输出: promise | normal | then | wait |
  • setTimeout会将代码执行推到线程末端,处在第三批

环境5 多个then

let promise = new Promise((resolve, reject) => {
  showNode.innerHTML += "promise | ";
  resolve("then |")
})

promise.then((res) => {
  showNode.innerHTML += res;
  return " next then | ";                       // 新加代码
}).then((res) => {                              // 新加代码
  showNode.innerHTML += res;                    // 新加代码
  return " next next then | ";                  // 新加代码
}).then((res) => {                              // 新加代码
  showNode.innerHTML += res;                    // 新加代码
})

showNode.innerHTML += "normal | ";

setTimeout(() => { showNode.innerHTML += "wait | " }, 0)

// 输出: promise | normal | then | next then | next next then | wait |
  • 按照之前提到的内容,正常顺序执行的代码在第一批,then在第二批,setTimeout在第三批,
  • 第一批完成后执行第二批,第二批完成后执行第三批


promise中 的 setTimeout:使用setTimeout后,都被提到进程末尾,然后按照正常代码那样顺序输出

环境6 promise 中加入 setTimeout

let promise = new Promise((resolve, reject) => {
  // 新加代码
  setTimeout(() => { showNode.innerHTML += "promise wait | "; }, 0) 
  showNode.innerHTML += "promise | ";
  resolve("then |")
})

promise.then((res) => {
  showNode.innerHTML += res;
  return " next then | ";
}).then((res) => {
  showNode.innerHTML += res;
  return " next next then | ";
}).then((res) => {
  showNode.innerHTML += res;
})

showNode.innerHTML += "normal | ";

setTimeout(() => { showNode.innerHTML += "wait | " }, 0)

// 输出:promise | normal | then | next then | next next then |
//      promise wait | wait |
  • 因为Promise在第一批,Promise中设置的setTimeout被提到了第三批
  • 在第三批执行过程中,流程也是从上往下顺序执行

环境7 外部setTimeout 在 promise 中的 setTimeout 之前

showNode.innerHTML += "normal | ";

setTimeout(() => { showNode.innerHTML += "wait | " }, 0)  // 代码位置更改

let promise = new Promise((resolve, reject) => {
  setTimeout(() => { showNode.innerHTML += "promise wait | "; }, 0)  
  showNode.innerHTML += "promise | ";
  resolve("then |")
})

promise.then((res) => {
  showNode.innerHTML += res;
  return " next then | ";
}).then((res) => {
  showNode.innerHTML += res;
  return " next next then | ";
}).then((res) => {
  showNode.innerHTML += res;
})

// 输出: normal | promise | then | next then | next next then | wait | 
//       promise wait |

环境8 promose中用setTimeout包裹resolve

showNode.innerHTML += "normal | ";

setTimeout(() => { showNode.innerHTML += "wait | " }, 0)

let promise = new Promise((resolve, reject) => {
  showNode.innerHTML += "promise | ";
  setTimeout(() => { resolve("then |"); }, 0)     // 代码修改
})

promise.then((res) => {
  showNode.innerHTML += res;
  return " next then | ";
}).then((res) => {
  showNode.innerHTML += res;
  return " next next then | ";
}).then((res) => {
  showNode.innerHTML += res;
})

// 输出: normal | promise | wait | then | next then | next next then |
  • 还是按照批次来思考, setTimeout在第一批中设定,里面执行的函数被提到第三批中。
  • promise只有状态变为 fulfilled 后才会触发相应的 then
  • 在第一批中将控制状态的 resolve() 提到第三批,等于将原本的第二批 then 所执行的内容全部提到第三批。
  • 第一批为正常顺序执行的代码,第二批为空,第三批按照从上到下的顺序执行。

环境9 将Promise外的setTimeout下移

showNode.innerHTML += "normal | ";

let promise = new Promise((resolve, reject) => {
  showNode.innerHTML += "promise | ";
  setTimeout(() => { resolve("then |"); }, 0)     
})

promise.then((res) => {
  showNode.innerHTML += res;
  return " next then | ";
}).then((res) => {
  showNode.innerHTML += res;
  return " next next then | ";
}).then((res) => {
  showNode.innerHTML += res;
})

setTimeout(() => { showNode.innerHTML += "wait | " }, 0) // 代码位置更改

// 输出: normal | promise | then | next then | next next then | wait |

环境10 then中的setTimeout

showNode.innerHTML += "normal | ";

let promise = new Promise((resolve, reject) => {
  showNode.innerHTML += "promise | ";
  setTimeout(() => { resolve("then |"); }, 0)
})

promise.then((res) => {
  // 新加代码
  setTimeout(() => { showNode.innerHTML += 'then wait | ' }, 0)  
  showNode.innerHTML += res;
  return " next then | ";
}).then((res) => {
  showNode.innerHTML += res;
  return " next next then | ";
}).then((res) => {
  showNode.innerHTML += res;
})

setTimeout(() => { showNode.innerHTML += "wait | " }, 0)

// 输出: normal | promise | then | next then | next next then | wait | 
//      then wait |
  • 第一批:正常顺序执行代码。
  • 第二批:空
  • 第三批:被setTimeout提到线程末尾的
  • 第四批:在then中被setTimeout提到线程末尾

——————————

全文到此结束

本文按照自己的理解,按照 “分批次” 的思想进行讲解,希望能让文章显得更加通俗易懂。

如果有什么不对的地方,请在评论区指出,或者私信我。

你的建议可以让我的下一篇文章更加精彩


Thank you for watching