# Promise, Async / Await ###### tags: `JavaScript` `Asynchronous` ## 前言: 程式碼到底是怎麼跑的? ![](https://i.imgur.com/dPeIO0F.png) #### 【1】Execution Context 執行環境 > 所有程式碼都必須在環境(Execution Context)中執行,而執行環境又分兩種: - Global Execution Context 執行全域環境(一開始) - Function Execution Context 執行函式環境(當函式被呼叫) > 每個環境的執行都會經歷兩個階段: > - Creation Phase 創造階段 > ![](https://i.imgur.com/aHUq5e7.png) > - Execution Phase 執行階段 > 一行一行讀取程式碼,如果在這個環境下找不到需要的參數,就會繼續往外層、往全域一層一層的去找,如果正常的執行了,那麼這個執行環境就會跳出 (pop off) 執行堆疊 (stack) 中。 ![](https://miro.medium.com/max/1400/1*nMAYYF1N04SjoRfdRmp4NA.gif) :::spoiler 詳細創造環境流程 ![](https://i.imgur.com/RNdC1z8.png) ::: #### 【2】Execution stack 執行堆疊 >- JavaScript 是單線程(single threaded runtime)的程式語言,所有的程式碼片段都會在堆疊(stack)中被執行,而且一次只會執行一個程式碼片段(one thing at a time)。 >- 他會優先處理最上層的環境,而正在執行中的環境又被稱為 現行環境 (Active context)。 >- 執行到每一個函式中的 return 或結尾時,這個函式便會跳離(pop off)堆疊。 ![](https://i.imgur.com/ANR5AOd.png) #### 【!】Blocking 阻塞 > - 當執行程式碼片段需要等待很長一段時間,或好像「卡住」的一種現象,以網站為例,當堆疊被阻塞(stack blocked),會使瀏覽器無法繼續渲染頁面,導致頁面「停滯」。 > - 為了要解決堆疊被阻塞的問題,我們會使用非同步的方法。 :::info **Asynchronous 非同步** 雖然 JavaScript的執行環境(Runtime)一次只能做一件事,但瀏覽器提供了更多不同的 API 讓我們使用,進而讓我們可以透過 event loop 搭配非同步的方式同時處理多個事項。 ::: #### 【!】Event Queue 事件佇列(瀏覽器提供) > 專門用來存放這些非同步的函式,等到整個主執行環境運行結束以後,才會開始依序執行事件儲列裡面的函式。(Event Queue 與 Stack 執行順序相反,先進來的會先被執行) #### 【!】Event Loop 事件循環(瀏覽器提供) > 事件循環(event loop)的作用很簡單,如果堆疊(stack)是空的,它便把佇列(queue)中的第一個項目放到堆疊當中;堆疊(stack)便會去執行這個項目。 :::warning 困擾的開始:非同步的流程控制 ::: ```javascript= function delayAdd(n1,n2,delayTime){ window.setTimeout(function(){ return n1+n2 },delayTime) } function test(){ let result=delayAdd(3,4,2000) console.log(result) } test() //undefinded🥺 ``` ## Asynchronous Callbacks 非同步回呼 > 回呼函式常常被使用在非同步的流程完成後需執行的動作,該情境被稱為「 asynchronous callbacks 」。 #### 補充: Callback 回呼函式 >函式中傳入的參數為函式的形式時,該函式參數即為「 回呼函式 」。 >A callback function is a function passed into another function as an argument, which is then invoked inside the outer function to complete some kind of routine or action. ### 常見非同步回呼 - **計時器** ```javascript= // setTimeout: 1秒後執行callback函式 window.setTimeout(callback,100) // setInterval: 每1秒執行一次callback函式 window.setInterval(callback,100) ``` - **事件處理器** ```javascript= button.addEventListener("click",callback) ``` - **網路請求** ```javascript= // XMLHttpRequest function getCurrentVersionNumber(versionCallback){ // 發送網路請求 let request = new XMLHttpRequest(); request.open("GET","http://www.example.com"); request.send(); // 請求成功 request.omload=function(){ if (request.readyState === 4){ if(request.status===200){ let currentVersion = parseFloat(request.responseText) versionCallback(null,currentVersion) } else{ versionCallback(response.statusText,null) } } } // 請求失敗 request.onerror = request.ontimeout=function(e){ versionCallback(e.type,null) } } ``` **`request.readyState`: 處理階段** >0:尚未讀取 >1:讀取中 >2:已下載完畢 >3:資訊交換中 >4:處理完畢 **`request.status`: HTTP協定的狀態碼** > 200:連線成功 > 404:連線錯誤 ### 😈 Callback Hell 回呼地獄 - 巢狀結構的 callback function 語法。(一個callback裡面還有其他callbacks) - 為了強制流程的順序,需大量使用判斷式。 - 傳入過多的 CallBack function。 ![](https://i.imgur.com/3N9awuC.png) ![](https://i.imgur.com/4kX1WOr.png) ## ES6 Promise 物件 > 表示「 單一 」非同步運算的最終完成或失敗的物件。 > (因此不會在將Promise用於按鈕事件等等,需重複觸發的情境) - 語法結構較為線性(Promise Chain),容易閱讀。 - 標準化錯誤的處理方式。 - 串接Promise的技巧:Promise.all, Promise.race。 ```javascript= // callback寫法 function delayAdd(n1,n2,delayTime,callback){ window.setTimeout(function(){ callback(n1+n2) },delayTime) } function test(){ delayAdd(3,4,2000,function(result){ console.log(result) }) } // Promise物件 function delayAdd(n1,n2,delayTime){ return new Promise(function(resolve,reject){ window.setTimeout(function(){ resolve(n1+n2) },delayTime) } function test(){ let promise=delayTime(3,4,2000) promise.then(function(result){ console.log(result) }) } ``` ## ES7 async / await >- async / await 是一個簡化Promise的語法糖。 >- async 用來宣告一個非同步函式,讓這個函式本體是屬於非同步,但以看似同步的方式運行程式碼。 >- await 則可以暫停非同步函式的運行(中止 Promise 的運行),直到非同步流程結束。 - async(function),await(運算子) 會一起被使用。 - 使用 async 宣告的函式必須為Promise物件 - 由於宣告為 async 的函式會被視為一個同步函式,所以當發生錯誤時,程式會全部停止運作!因此通常會搭配 try...catch 使用。 ```javascript= function delayAdd(n1,n2,delayTime){ return new Promise(function(resolve,reject){ window.setTimeout(function(){ resolve(n1+n2) },delayTime) } async function test(){ try{ let result = await delayTime(3,4,2000) //7 console.log(result) } catch(error){ console.log("error",error) } }) } ``` ### 資料參考 [回呼函式 Callbacks、Promises 物件、Async/Await 非同步流程控制 - 彭彭直播](https://www.youtube.com/watch?v=NOprCnnjHm0&ab_channel=%E5%BD%AD%E5%BD%AD%E7%9A%84%E8%AA%B2%E7%A8%8B) [MDN Promise](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Guide/Using_promises) [秒懂!JavaSript 執行環境與堆疊](https://medium.com/%E9%AD%94%E9%AC%BC%E8%97%8F%E5%9C%A8%E7%A8%8B%E5%BC%8F%E7%B4%B0%E7%AF%80%E8%A3%A1/%E6%B7%BA%E8%AB%87-javascript-%E5%9F%B7%E8%A1%8C%E7%92%B0%E5%A2%83-2976b3eaf248 ) https://hsiangfeng.github.io/javascript/20190530/3821927811/ https://itbilu.com/javascript/js/41KMSZ9a.html https://cythilya.github.io/2018/10/31/promise/ https://pjchender.blogspot.com/2017/08/javascript-learn-event-loop-stack-queue.html