本文原理:
JavaScript是单线程,setTimeout会让代码置于线程末尾。
Promise建立后与正常代码一样顺序执行。
Promise在状态变为resolve后才会触发后续的then
- 读完本文大约需要15-25分钟
- 本文前置知识:基础js,Promise对象,ES6其他基础知识
- 阅读难度:初级
- 本文所有代码及输出结果都写了出来,可以不用编译器编译,直接浏览文章
如果你知道以下代码的执行结果,那么本文对你的收获就比较有限
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线程的知识可以移步
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提到线程末尾
全文到此结束
本文按照自己的理解,按照 “分批次” 的思想进行讲解,希望能让文章显得更加通俗易懂。
如果有什么不对的地方,请在评论区指出,或者私信我。
你的建议可以让我的下一篇文章更加精彩