promise,then,setTimeout -- 細緻探討執行流程

ParaSLee發表於2018-05-16

本文原理:

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執行緒中沒有任何同步程式碼要執行的前提下才會執行非同步程式碼

promise,then,setTimeout -- 細緻探討執行流程

有關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提到執行緒末尾

——————————

全文到此結束

本文按照自己的理解,按照 “分批次” 的思想進行講解,希望能讓文章顯得更加通俗易懂。

promise,then,setTimeout -- 細緻探討執行流程

如果有什麼不對的地方,請在評論區指出,或者私信我。

你的建議可以讓我的下一篇文章更加精彩


Thank you for watching

相關文章