--- title: Promise / fetch / Async-Await (05/09) tags: 107-2 Ric's Web Programming slideOptions: theme: beige transition: 'fade' slidenumber: true # parallaxBackgroundImage: 'https://s3.amazonaws.com/hakim-static/reveal-js/reveal-parallax-1.jpg' --- ### Promise / fetch / Async-Await ![](https://i.imgur.com/2WlXLO0.png) Spring 2019 。 Ric Huang --- ### Synchronous or Asynchronous? * 大部分的程式語言都是 synchronous (同步) 的在執行,也就是說,在同一個執行緒裡面的指令是依序的被執行,在前面一個指令執行結束之前,下一個指令是被 block 住的,必須等到前一個指令完成之後,下一個指令才會被執行。 * 例如,在 C/C++ 的程式裡當它在忙著做運算或 I/O 的時候,這時候輸入任何東西它是不理你的 ---- ### Synchronous or Asynchronous? * 但在許多的應用,尤其像是在網路的時代,如果一個網頁開啟的時候需要載入一些圖片、影片,你會期待網頁還是會先 show 出來,而只有部分圖片/影片的地方會 load 一下才出來。你不會希望看到的頁面是一片慘白,而必須等到所有圖片、影片都 load 好之後,頁面再 show 出來 ---- ### Asynchronous Methods/Functions * 我們需要非同步(asynchronous)的執行程序,讓一些較花時間的指令被呼叫時,可以以 non-blocking 的方式執行,也就是說讓程式可以當下就把執行的控制權要回來(以繼續執行下一個指令),並且 branch 出另外的執行緒來執行這個較花時間的指令,而且雙方約定好一個通知的方式 (e.g. callback function),讓對方完成的時候(不管是成功或是失敗),本來的程式都會立即知道 * 但 "polling" 通常不會是解決非同步程序的選項 ---- ### 概念上像這樣... ![](https://i.imgur.com/P7m7Dop.png) --- ### 所以, ### JavaScript 是同步還是非同步? ---- * 這樣問其實不精確。JavaScript,如同其他 programming language 一樣,都是同步/blocking 在執行的,但它提供了 non-blocking 的非同步 I/O methods 讓程式在進行 I/O 動作時,得以不會因為 I/O 的速度較慢而影響整體的使用者體驗 * 非同步 I/O 例如:AJAX, WebSocket, DB access, 'fs' module in node.js ---- ### Try this... // Click when start executing. // See how the message is printed. ```javascript function waitThreeSeconds(){ var ms = 3000 + new Date().getTime(); while(new Date() < ms) {} console.log("finished function"); } function clickHandler() { console.log("click event!"); } document.addEventListener('click', clickHandler); console.log("started execution"); waitThreeSeconds(); console.log("finished execution"); ``` => JavaScript executes synchronously. ---- ### Small tip: 善用 function hoist 有沒有看起來不一樣? ```javascript document.addEventListener('click', clickHandler); console.log("started execution"); waitThreeSeconds(); console.log("finished execution"); function waitThreeSeconds(){ var ms = 3000 + new Date().getTime(); while(new Date() < ms) {} console.log("finished function"); } function clickHandler() { console.log("click event!"); } ``` --- ### Recall: Callback Functions * 定義一個 callback function f(), 並且作為參數傳入 g(), 讓我們的程式在執行到 g() 的時候,可以不會因為 g() 的執行時間太久而停下來 ```javascript let url = 'https://some.web.address.com'; let error, response, result; console.log("start execution"); const result = someAsyncFunction(url, callbackFunction); console.log("someAsyncFunction is called"); // When "someAsyncFunction()" is called, // "callbackFunction()" is put in an "event loop" // and gets executed once someAsyncFunction is done function callbackFunction(error, response) { if (error) { /* handle error here */ } else { /* put some info in the response */ } } ``` ---- ### Callback Hell * This kind of callback mechanism is nortorious for the potential "callback hell" situation * A requests B; while waiting for B, do something * B requests C; while waiting for C, do something * C requests D... * Process C's result. Determine success or fail. * Process B's result. Determine success or fail. ---- ### 像這樣... [![](https://i.imgur.com/VrE4anl.png)](http://blog.mclain.ca/assets/images/callbackhell.png) ---- ### To prevent "callback hell" ([ref](http://callbackhell.com/)) 1. Keep your code shallow 2. Modularize your code 3. Handle every single error ---- * This example uses browser-request to make an AJAX request to a server. However, this style may goes into deep callback hell. ```javascript var form = document.querySelector('form'); form.onsubmit = function (submitEvent) { var name = document.querySelector('input').value; request({ uri: "http://example.com/upload", body: name, method: "POST" }, function (err, response, body) { var statusMessage = document.querySelector('.status'); if (err) return (statusMessage.value = err); statusMessage.value = body; } ) } ``` ---- ### 1. Keep your code shallow * To fix it, first name the functions and take advantage of function hoisting to keep the code shallow. ```javascript document.querySelector('form').onsubmit = formSubmit function formSubmit (submitEvent) { var name = document.querySelector('input').value request({ uri: "http://example.com/upload", body: name, method: "POST" }, postResponse) } function postResponse (err, response, body) { var statusMessage = document.querySelector('.status') if (err) return statusMessage.value = err statusMessage.value = body } ``` ---- ### 2. Modularize your code * Secondly, create JavaScript modules — by re-organizing functions into smaller files. * The above example can be simplified as below when the callback functions are defined in another files. ```javascript var formUploader = require('formuploader'); document.querySelector('form').onsubmit = formUploader.submit; ``` ---- // “formuploader.js” is where the modules are defined. ```javascript module.exports.submit = formSubmit function formSubmit (submitEvent) { var name = document.querySelector('input').value request({ uri: "http://example.com/upload", body: name, method: "POST" }, postResponse) } function postResponse (err, response, body) { var statusMessage = document.querySelector('.status') if (err) return statusMessage.value = err statusMessage.value = body } ``` ---- ### 3. Handle every single error * Note: When dealing with callbacks, you are by definition dealing with tasks that get dispatched, go off and do something in the background, and then complete successfully or abort due to failure. * What happens if any of them encounters errors? What happens if you don’t handle the error and continue executing the callback functions? ---- // Important: Apply the Node.js style where the first argument to the callback is always reserved for an error. ```javascript var fs = require('fs'); fs.readFile('/Does/not/exist', handleFile); function handleFile (error, file) { if (error) return console.error('Uhoh, there was an error', error); // otherwise, continue on and use `file` in your code } ``` --- ### Introducing **Promise** * 傳統用 callback 來作為 asynchronous functions 的回應方法很容易讓 code 變得很混亂、破碎,且不容易將不同非同步事件之間的關係描述得很乾淨 * **“Promise”** 就是為了讓在 JavaScript 裡頭的 asynchronous 可以 handle 的比較乾淨 ---- * **Promise 物件**:定義好一個 asynchronous function 執行之後成功或是失敗的狀態處理 * 使用情境:將 Promise 物件的 **.then()**, **.catch()** 加入此 asynchronous function 應該要被呼叫的地方 * Note: ES6 已經把 promise.js 納入標準 ---- * 想像一下你是個孩子,你媽承諾(Promise)你下個禮拜會送你一隻新手機(some async event)。 * 現在你並不知道下個禮拜你會不會拿到手機。你媽可能真的買了新手機給你,或者因為你惹她不開心而取消了這個承諾 (resolved, or error) * 為了把這段人生中小小的事件定義好(所以你可以繼續專心的生活),你將問了媽媽以後會發生的各種情況寫成一個 JavaScript function: ```javascript var askMom = function willIGetNewPhone() { … } askMom(); // execution will be blocked for a week... ``` <small>ref: https://andyyou.github.io/2017/06/27/js-promise/</small> ---- * 基本上一個像是 willIGetNewPhone() 的非同步事件會有三種狀態: * Pending: 未發生、等待的狀態。到下週前,你還不知道這件事會怎樣。 * Resolved 完成/履行承諾。你媽真的買了手機給你。 * Rejected 拒絕承諾。沒收到手機,因為你惹她不開心而取消了這個承諾。 * 我們將把 willIGetNewPhone() 改宣告一個 Promise 物件,來定義上面三種狀態。 ---- ```javascript var isMomHappy = true; var willIGetNewPhone = new Promise(function (resolve, reject) { if (isMomHappy) { var phone = { id: 123 }; // your dream phone resolve(phone); } else { var reason = new Error('Mom is unhappy'); reject(reason); } }); ``` ---- * 定義 Promise 物件的語法: * new Promise(function (resolve, reject) { … }); * 如果一個 Promise 執行成功要在內部 function 呼叫 resolve (成功結果),如果結果是失敗則呼叫 reject (失敗結果)。 * 一個 Promise 物件表達的是一件非同步的操作最終的結果,可以是成功或失敗。 ---- * 用 Promise 定義好 willIGetNewPhone() 之後,我們就可以來改寫 askMom() — ```javascript var askMom = function () { willIGetNewPhone .then(function(fullfilled) { …}) // fullfilled = phone .catch(function(error) { …}); // error = reason }; askMom(); ``` * 在 .then 中使用 function(fullfilled){},而這個 fullfilled 就是從 Promise 的 resolve(成功結果) 傳來的結果 (phone) * 在 .catch 中我們使用了 function(error) {}。而這個 error 就是從 Promise 的 reject(失敗結果) 傳來的 (reason) ---- ### Putting Things Together * 把 asynchronous function 包成 Promise 物件,用 .then() 與 .catch() 來處理成功與失敗的結果。 ```javascript const handleResolved = (data) => {...} const handleRejected = (err) => {...} let myPromise = new Promise((resolve, reject) => { try { const data = getData(); resolve(data); } catch (err) { reject(err); } }); myPromise .then(handleResolved(data)) .catch(handleRejected(err)); ``` ---- ### Chain of Promises * 假設有一連串的 asynchronous functions 要執行,可以把他們的 Promise 串聯起來 — ```javascript doSomething() .then(result => doSomethingElse(result)) .then(newResult => doThirdThing(newResult)) .then(finalResult => {...}) .catch(failureCallback); ``` * 其中 doSomething(), doSomethingElse(), doThirdThing() 就是用 Promise 包起來的三個 asynchronous functions ---- ### .catch() 後面還是可以接東西 * See this example — ```javascript new Promise((resolve, reject) => { console.log('Initial'); resolve(); }).then(() => { throw new Error('Something failed'); console.log('Do this'); }).catch(() => { console.log('Do that'); }).then(() => { console.log('Do this whatever happened before'); }); ``` * Output — ``` Initial Do that Do this whatever happened before ``` ---- ### Promise.all() * p1 & p2 都要成功 ```javascript const p1 = new Promise((resolve, reject) => { setTimeout(resolve, 500, "one"); }); const p2 = new Promise((resolve, reject) => { setTimeout(resolve, 100, "two"); }); Promise.all([p1, p2]).then((value) => { console.log(value); }); ``` * Output: Array [ "one", "two" ] ---- ### Promise.race() * 看誰先成功 ```javascript const p1 = new Promise((resolve, reject) => { setTimeout(resolve, 500, "one"); }); const p2 = new Promise((resolve, reject) => { setTimeout(resolve, 100, "two"); }); Promise.race([p1, p2]).then((value) => { console.log(value); // Both resolve, but p2 is faster }); ``` * Output: two ---- ### Promise 好好學,寫 js 沒煩惱。 --- ### 一個很有用的 Promise — fetch() ([ref](https://andyyou.github.io/2017/06/27/js-promise/)) * 有聽過/用過 XMLHttpRequest() 嗎? * 在 IE7 (2006) 年就有的 API * 命名有些問題 * API 太過低階 ```javascript function reqListener () { console.log(this.responseText); } var oReq = new XMLHttpRequest(); oReq.addEventListener("load", reqListener); oReq.open("GET", "http://www.example.org/example.txt"); oReq.send(); ``` ---- ### 用 fetch() 取代 XMLHttpRequest() —> 回傳 Promise() ```javascript fetch("https://www.google.com") .then((res)=>console.log(res.status)) .catch((err) => console.log('Error :', err)); // output: 200 ``` * 常用的 res.text() res.json() 都是回傳Promise() ```javascript fetch("https://www.google.com") .then((res)=>console.log(res.text())) .catch((err) => console.log('Error :', err)); // output a Promise() ``` ---- ### 用 fetch() 送 POST ```javascript // fetch(url, object) fetch(url, { method: 'post', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' }, body: JSON.stringify({ name: 'Hubot', login: 'hubot' }) }); // method: 可以是 get, post, put, delete // headers: HTTP request header // body: JSON.stringify 轉成 JSON 字串 ``` --- ### From Promise to Async/Await [![](https://i.imgur.com/sL7ItUE.png)](https://medium.com/@peterchang_82818/javascript-es7-async-await-%E6%95%99%E5%AD%B8-703473854f29-tutorial-example-703473854f29) --- * 雖然 ES7 原生支援 async/await, 但每個瀏覽器的支援程度不同,使用前還是要先查一下,否則就要用 Babel. [![](https://i.imgur.com/QnMFUKB.png)](https://caniuse.com/#search=async) ---- ### Promise vs. Async/Await * Promise ```javascript Doctor.findById() .then(doctor => { // some logic doctor... return somePromise; }).then(...); ``` * Async/Await ```javascript const f = async () => { const doctor = await Doctor.findById(); // some logic doctor... } f(); ``` ---- ### Promise vs. Async/Await * Async/Await 的目的在讓 Promise 概念的使用更直覺 * 在 top-level function 前面加個 async ```javascript async function name([param[, param[, ... param]]]) { statements } ``` * 用 await 把原先非同步結束後才要在 .then() 執行的程式碼直接隔開,平行的放到下面 ```javascript async function name([param[, param[, ... param]]]) { let ret = await doSomethingTakesTime(); // this part does not run until await finishes } ``` * 基本上 async 匡了一個 top-level async 的區域,然後裡面透過 await 變成是 synchronous ---- #### Promise vs. Async/Await ([ref](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Statements/async_function)) ![](https://i.imgur.com/pT4PcfS.png) ![](https://i.imgur.com/VnguAhf.png) ---- * Await sequentially ![](https://i.imgur.com/RiQbGhv.png) * Await parallelly ![](https://i.imgur.com/rn7Y4bh.png) ---- * Nested Await ![](https://i.imgur.com/C8ZQqvm.png) --- ### 關於期中 Project ([ref](https://hackmd.io/SABySOxaQH-r_HODiPvnRA)) ![](https://i.imgur.com/ULZ2GMD.png) ---- ### General Rules * 題目自訂,一個人一組, 佔學期成績 20% * Requirements * Client-side programs in React.js * Server-side programs in Node.js * Recommended * Database to store persistent data * Use "Promise" or "Async/Await" to ensure asynchronous non-blocking communications * Deadline: 9pm, Tuesday, 05/21 ---- ### 關於 Github Repo * 建立一個 public 的 repo (平行於 **WebProg2019**),命名為:**midterm_\<yourAppName>** * 請確認可以使用 "npm install" 安裝你的 app, 並且可以使用 "npm start" 執行你的 app * 如有其他的 scripts 或者是執行時的注意事項, 請在 README.md 中描述清楚 * Project 的目錄與檔案命名名稱不限,但請盡量 follow 上課所講的 conventions (e.g. public, src, containers, components) * Optional: Deploy 到 cloud 並提供固定網址以利他人操作測試 ---- ### 關於使用他人的框架與模組 * 歡迎使用 React/Node family 相關的 framework (e.g. Next.js),現代化的 web programming 不鼓勵土法煉鋼,多多利用別人開發好的套件/工具,才能達到事半功倍的效果 * 但為了收斂大家的學習,也確保大家有把我們教的 React/Node 真正的趁這個 project 好好複習一下,所以請不要使用別的框架 (e.g. Angular.js, PhP) * 也歡迎參考別人的 open source 專案,在他的上面去開發你的應用 * 但請在 README.md 裏頭講清楚你的修改與貢獻 ---- ### README.md * 請在 Project 的根目錄編輯一份 README.md,以利助教快速了解你的 project 內容 * 內容應至少包含: * 題目名稱 * 一句話描述這個 project 在做什麼 * (Optional) Deployed 連結 * 使用/操作方式 * 其他說明 * 使用與參考之框架/模組/原始碼 * 我的貢獻 * 心得 ---- ### 繳交注意事項 * 請在 deadline 以前將 repo 連結 PO 至 FB 社團 ([link](https://www.facebook.com/groups/NTURicWebProg/)),發文時請將 README.md 的內容複製貼至 PO 文 * PO 文至 FB 社團即算完成繳交,但請勿在未完成之前 PO 文,以免助教下載到屍體版本 * 評分標準:題目深度(30%)、完成度(40%)、Coding 品質(20%)、是否符合規定(10%) --- ### That's it!! ### Enjoy hacking the project!!