[JS] 同步非同步霧煞煞(上)-AJAX
前言
接觸Js一段時間後,開始學習如何串接API,剛開始時就直接先被介紹用了Axios這個好用的套件,但原理等等還是不能理解,因此看了很多文章文件,才摸索到原來同步,非同步,Event Queue,Ajax,XMLHttpRequest,fetch,promise,Async,Await等等之間的關聯性,才把觀念串通。這篇會來把我所認知的概念整理起來。
同步Synchronous與非同步Asynchronous
Javascript是單執行緒 (single threaded)、同步(Synchronous) 的程式語言,一次只能做一件事,但為何google上很多人都說Javascript是非同步呢?其實是使用setTimeout、AJAX、Promise等而有非同步的特性,但其實這些方法並不是由javascript程式語言本身提供的,不受單執行緒限制,他們會被放到Event Queue中。
我們目前常用的Javascript執行環境有瀏覽器和Node.js。在瀏覽器上透過document來操作DOM,document也不是程式語言本身提供,而是瀏覽器,因此click這個事件監聽也是非同步特性。在瀏覽器上其他方法還有做AJAX的 XMLHttpRequest, fetch, Promise,計時用的setTimeout和setInterval。執行環境用 <script src="all.js">
引入在html上。而在Node.js上則有http可做Ajax寫伺服器, os, fs來控制檔案介面,同時也有SetTimeout。執行方式安裝node.js用node all.js來執行。
以前大學的暑假,曾經在美國的麥當勞工作過,櫃檯工作的流程就很像是非同步的模式。覺得中文翻譯過來的非同步很容易讓人誤解成不能同步工作。同步應該要翻成只能一步一步做,上一步會阻礙下一步(blocking特性),非同步則為可以分開做…不會阻礙下一步(non-blocking特性),同步非同步這個字義讓一開始的我一直搞混。
同步synchronous模式
- 櫃檯小姐把你要的餐點輸入點餐機: 起司漢堡一個、薯條一份
- 櫃檯小姐把Order傳送給漢堡流水線去要漢堡
- 等待漢堡完成
- 漢堡製作好了,櫃檯小姐給你
- 櫃檯小姐把Order傳送給薯條台去要薯條
- 等待薯條完成
- 薯條製作好了,櫃檯小姐給你
- 結帳
非同步Asynchronous模式
櫃檯小姐 (Call Stack)
- 櫃檯小姐把你要的餐點輸入點餐機: 起司漢堡一個、薯條一份
- 櫃檯小姐把Order傳送給漢堡流水線去要漢堡 (WebAPIs Request)
- 櫃檯小姐把Order傳送給薯條台去要薯條 (WebAPIs Request)
- 櫃檯小姐幫你結帳
- 收到薯條台通知,薯條炸好了
- 結帳完成
- 櫃檯小姐沒事了,出餐口機器手臂把薯條給櫃檯小姐後,放在給你的餐盤上 (callback function)
- 收到廚房通知說漢堡做好了
- 櫃檯小姐沒事了,出餐口機器手臂把漢堡給櫃檯小姐後,放在給你的餐盤上 (callback function)
- 出餐完成
–廚房端和出餐口(Event Queue)
廚房端 (WebAPIs)
- 漢堡流水線和薯條台接收到櫃檯小姐的Order
- 各自開始製作,漢堡流水線中有人去上廁所需要3分鐘才會開始製作(setTimeout 3分鐘)
- 各部門製作好後各自送到出餐口去(response),製作失敗(error)
出餐口 (Callback Queue or Task Queue)
- 薯條製作好了,等待櫃檯小姐拿取,但他好像還在忙喔!
- 出餐口機器手臂(Event Loop)偵測到櫃檯小姐結帳完了沒事(Call Stack空了),把薯條給櫃檯小姐
- 漢堡製作好了,等待櫃檯小姐拿取
- 出餐口機器手臂(Event Loop)偵測到櫃檯小姐給完薯條了沒事(Call Stack空了),把漢堡給櫃檯小姐
以下例子取自Tommy大的教學,非同步最常見的例子,setTimeOut設定為0秒但還是會被排到Event Queue中等待console.log(‘C’)執行完後才會再執行,因此會得到 A C B的結果。
1 | console.log('A'); |
下面這個例子,setTimeOut設定為3秒,下方接一個5秒回圈後執行C,在5秒的過程中setTimeout在經過3秒後便開始在WebApis端執行並把結果放在Callback Queue中,等C一執行完成後Call Stack空了,EventLoop變馬上把Callback Function放入Stack中,B則馬上跳出。
1 | console.log('A'); |
AJAX (Asynchronous JavaScript and XML)
直接翻譯過來就是 非同步的 JavaScript 與 XML 技術,可以即時向伺服器傳送
(request)並取回必須的資料(response),XML則是一種早期比較常在用的資料格式,現在比較多人使用的是JSON格式,不知道這樣AJAX要不要改成AJAJ…XD
伺服器request的底層方法:
衍生的request的library套件:
以下兩個網站都可以用來製作測試用的 API,也可以客製化 AJAX 成功後的回傳訊息。
這邊會用隨機用戶產生器來做練習
XMLHttpRequest (可支援 IE 7 以上)
原生的JS,但缺點是寫法會比較難閱讀和複雜
XMLHttpRequest DEMO 非同步 GET 請求
AJAX 行為會被丟到 Queue,等取得回應後才回到 JS 執行緒。
1 | var xhr = new XMLHttpRequest(); |
DEMO 同步 GET 請求
1 | console.log('start'); |
DEMO 非同步 POST 請求
1 | // 如何把參數發送到後端 |
1 | const xhr = new XMLHttpRequest(); |
HTML5 Fetch API (IE 11 以下都不支援)
ES6的新語法,搭配 Promise 作回應,支持 async/await.then()接續.catch()接收錯誤,回傳的是 ReadableStream 物件,需要使用不同資料類型(json,blob)使用對應方法,才能正確取得資料物件。 但使用上也有一些需要注意的:
- IE 11以下都不支援
- 回傳的 promise 不會 reject HTTP 的 error status,就算是 HTTP 404 或 500 也一樣。相反地,它會正常地 resolve,並把 ok status 設為 false。會讓它發生 reject 的只有網路錯誤或其他會中斷 request 的情況,需要封裝去處理。
- 預設是沒有帶cookie,需要新增配置項。
- 不支援超時控制,使用setTimeout及Promise.reject的實現的超時控制並不能阻止請求過程繼續在後臺執行,造成了量的浪費。
- 不支援原生監測請求的進度,而XHR可以
Fetch 常用的 Request 屬性
url: 第一個參數,一定要填的項目,代表需要 fetch 對象的網址
method: GET、POST、PUT、DELETE、HEAD ( 預設 GET )
headers: 要求相關的 Headers 物件 ( 預設 {} )
mode: cors、no-cors、same-origin、navigate ( 預設 cors )
referrer: no-referrer、client 或某個網址 ( 預設 client )
credentials: omit、same-origin、include ( 預設 omit )
redirect: follow、error、manual ( 預設 manual )
cache: default、no-store、reload、no-cache、force-cache ( 預設 default )
body: 要加到要求中的內容 ( 如果 method 為 GET 或 HEAD 則不設定 )
Fetch 常用的 Response 屬性
headers: 包含與 response 相關的 Headers 物件
ok: 成功回傳 true,不成功回傳 false
status: 狀態代碼,成功為 200
statusText: 狀態文字,成功為 ok
type: response 的類型,例如 basic、cors…等
url: response 的 url
ReadableStream 物件
Fetch API 的 回傳為 ReadableStream 物件,我們無法直接讀取資料內容,而 ReadableStream 物件中可用以下對應的方法來取得資料 (https://developer.mozilla.org/zh-TW/docs/Web/API/Body):
arrayBuffer() 返回 Promise,Buffer,代表一段記憶體區塊,僅能透過 View 操作其內容。
formData() 返回 Promise,取得的資料格式將會是formData格式
json() 返回 Promise,取得的資料格式將會是JSON格式
text() 返回 Promise,取得的資料格式將會是純字串
blob() 返回 Promise,
可以將資料轉為blob物件,像是圖片就可以做這樣的轉換
(這裡的圖片並非指圖片路徑,而是圖片檔案本身)。
clone() 建立 Response 的複製物件
error() 返回 Response 的錯誤物件
1 | // unsplash 上的圖片 |
Fetch DEMO 發送 GET 請求
1 | const url = 'https://randomuser.me/api/'; |
Fetch DEMO 發送 POST 請求
1 | // 把資料參數傳到後端,一樣要先宣告 FormData |
1 | let url = `https://hexschool-tutorial.herokuapp.com/api/${type}`; |
jQuery Ajax
以XMLHttpRequest為基礎的library,使用簡單,但jQuery本身是針對MVC的程式設計,不符合現在前端MVVM的浪潮,因為現在有許多新的原生語法可取代JQuery,若單純只是使用Ajax卻引入整個jquery不太合理。
DEMO 發送 GET 請求
1 | $.ajax({ |
1 | $.ajax({ |
axios
axios 本質上也是對原生的 XMLHttpRequest 封裝,從 node.js 建立 http 請求,並可以支援 Promise,可取消請求(Promise無法),自動轉換JSON檔,客戶端支援防止CSRF,提供了一些併發請求的介面,axios 實體建立
DEMO 發送 GET 請求
1 | const url = 'https://randomuser.me/api/'; |
DEMO 發送 POST 請求
1 | const url = 'https://randomuser.me/api/'; |
執行多個併發請求
1 | function getData1() { |
參考資料
[JS] 同步非同步霧煞煞(上)-AJAX