{%hackmd BJqmTNgxD %} ASTROCamp 7th JavaScript === #### 透過 JavaScript 進行 HTTP 請求 & 非同步 --- ### 今天投影片連結 #### [https://ppt.cc/fLqfWx](https://ppt.cc/fLqfWx) ### 隨堂練習包 #### [https://ppt.cc/f514zx](https://ppt.cc/f514zx) --- ## HTTP request via JavaScript --- ### HTTP request #### [維基百科](https://zh.wikipedia.org/wiki/%E8%B6%85%E6%96%87%E6%9C%AC%E4%BC%A0%E8%BE%93%E5%8D%8F%E8%AE%AE) #### DevTool 的 Network 可以拿來觀察 --- ### `fetch(...)` ##### [MDN 文件](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) --- ### 在 `fetch(...)` 出現之前 ##### [`$.ajax()`](https://api.jquery.com/jquery.ajax/) ##### [`XMLHttpRequest`](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest) --- ### 今天為大家準備的 Posts API server #### [https://pastleo-posts-api.herokuapp.com/](https://pastleo-posts-api.herokuapp.com/) ##### [Git repository](https://github.com/5xTraining/pastleo-js-posts-api) --- ### Web API 常見的傳回格式:[JSON](https://zh.wikipedia.org/wiki/JSON) ```json [ { "id": 128, "title": "hello json!" } ] ``` --- ### `06_fetch-first-post` ##### 使用 `fetch` 抓取並顯示 API 回傳的第一篇文章 --- ### `06_fetch-first-post` ```javascript fetch(...) .then(request => request.json()) .then(posts => { ... }) ``` --- ### CORS #### [Cross-origin resource sharing](https://zh.wikipedia.org/zh-tw/%E8%B7%A8%E4%BE%86%E6%BA%90%E8%B3%87%E6%BA%90%E5%85%B1%E4%BA%AB) --- ### CORS 是怎麼運作的? ##### [設定 CORS headers](https://github.com/5xTraining/pastleo-js-posts-api/blob/bdcb851fb392b20179f9521584d67f35af9d37ec/app/controllers/posts_controller.rb#L99) ##### 一般是沒有 CORS headers ##### [以 Posts 另一組 API 為例](https://pastleo-posts-api.herokuapp.com/posts.json) --- ## Asynchronous / 非同步 --- ### 什麼是同步? ```javascript const two = 1 + 1; const post = fetchPostSync(); // IO operation postTitleA.textContent = post.title; ``` --- ### 同步的問題 ![](https://i.imgur.com/30qjpIX.jpg) --- ### 沒有回應的原因 #### JS 的特性: * 單執行緒 * [Run-to-completion](https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop#run-to-completion) #### input/output (IO) operation: 存取網路, 檔案等,通常 CPU 不忙 但是需要等待時間 --- #### 等待的時候可以去處理別的事情 ### 這就是 asynchronous 非同步 --- ### `07_sync-async-fetch` #### Callback: 給一個 function 來告知瀏覽器 IO 完成的時候要做什麼 --- ### 非同步寫法 ![](https://i.imgur.com/bSxnBAx.jpg) --- ### Callback hell <div style='font-size: 18px;'> ```javascript const xhr1 = new XMLHttpRequest(); xhr1.open('GET', /* ... */); xhr1.send(); xhr1.addEventListener('load',() => { const xhr2 = new XMLHttpRequest(); xhr2.open('GET', /* ... */); xhr2.send(); xhr2.addEventListener('load',() => { const xhr3 = new XMLHttpRequest(); xhr3.open('GET', /* ... */); xhr3.send(); xhr3.addEventListener('load',() => { const xhr4 = new XMLHttpRequest(); xhr4.open('GET', /* ... */); xhr4.send(); xhr4.addEventListener('load',() => { // ... }) }) }) }) ``` </div> --- ### 非同步寫法 ## [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) --- ### Promise * 你各位已經用過了 * 因為 `fetch` 就回傳 `Promise` * JavaScript 官方的非同步處理界面 * `.then(callback)` * 在 resolve 時執行 callback * `callback` 如果回傳 `Promise` * **可以繼續串 (chain)** * `.then(callback).then(callback)` --- ### `08_fetch-first-post-content` 取得文章列表之後,再抓文章內文來顯示 --- ### 非同步寫法 ## [`async` / `await`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function) --- ### `async` function * 寫法 * `async () => {}` * `async function() {}` * `async function name() {}` * 回傳 Promise * 可用 `await` 關鍵字 * `resolved = await promise` --- ### 改用 `async` / `await` #### `08_fetch-first-post-content` --- ## http `POST` via JavaScript #### `fetch(...)` 還有更多參數可用 --- ### `09_create_post` `fetch(url, options)` 的 `options` * [`url` 與 `posts index` 相同](https://pastleo-posts-api.herokuapp.com/api/posts) * `method: 'POST'` * `headers: { Authorization: '...' }` * `pastleo-js-posts-api-secret` * `body: new FormData(form)` * [FormData](https://developer.mozilla.org/zh-TW/docs/Web/API/FormData) --- ### `09_create_post` * 錯誤處理 * `response.ok` * 顯示成功/錯誤訊息 * Loading 效果 * 防止重複送出 * 成功後清除輸入 --- ### `new Promise(...)` #### 回家作業: <div style='font-size: 24px;'> 完成 `09_create-post`,並且把 `setTimeout` 包裝成 `Promise` 使得最後重設送出按鈕狀態的程式可以這樣改寫: ```diff -setTimeout(() => { - submitBtn.textContent = 'Submit'; - submitBtn.disabled = false; -}, 2000); +await timeoutPromise(2000); +submitBtn.textContent = 'Submit' +submitBtn.disabled = false; ``` [MDN 的 Promise Constructor 範例](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Global_Objects/Promise) </div> --- ## Recap * `fetch(...)` * CORS * sync/async: callback * Promise * `async` / `await`
{"metaMigratedAt":"2023-06-15T22:39:56.570Z","metaMigratedFrom":"YAML","title":"透過 JavaScript 進行 HTTP 請求 & 非同步 - ASTROCamp 7th JavaScript","breaks":true,"slideOptions":"{\"spotlight\":{\"enabled\":true,\"size\":80,\"presentingCursor\":\"default\",\"toggleSpotlightOnMouseDown\":false,\"spotlightOnKeyPressAndHold\":90,\"initialPresentationMode\":true,\"disablingUserSelect\":false,\"fadeInAndOut\":500}}","contributors":"[{\"id\":\"0eb274f7-a3a4-4c8e-b0d4-e5c08eaf9e72\",\"add\":6465,\"del\":1247}]"}
    397 views