Javascript非同步處理 / Promise / Async Await
Node.js線程模型
單線程且非同步?
請參閱資料
非同步程式碼之霧:Node.js 的事件迴圈與 EventEmitter
Javascript非同步程式與處理Callback Hell
使用setTimeout模擬ajax去server side取得message的資料後
每一個工作依賴於前一個先做完才能往下做
- 顯示取回的資料
- 跟之前的資料做加總
- 最後顯示總和
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| setTimeout(function () { var message = 1 console.log(message) var sum = message setTimeout(function () { var message = 1 console.log(message) sum += message setTimeout(function () { var message = 1 console.log(message) sum += message setTimeout(function () { console.log(message) sum += message console.log(sum) }, 1000) }, 1000) }, 1000) }, 1000)
|
如果有幾個非同步請求彼此相依,要依照順序執行時
這種一層一層弓形的callback function就會形成沒有可讀性,難維護的程式碼
稱為callback hell
例如:你有三個後端API
- 取得文章列表
- 取得某文章的回文列表
- 取得某回文的作者信箱
在前端就可能要依照順序呼叫這三個API就有可能寫出這樣的程式碼
如何處理?
Promise來拯救!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| var sum = 0 var first = function () { return new Promise(function (resolve) { setTimeout(function () { resolve(1) }, 1000) }) } var second = function () { return new Promise(function (resolve) { setTimeout(function () { resolve(1) }, 1000) }) } var third = function () { return new Promise(function (resolve) { setTimeout(function () { resolve(1) }, 1000) }) } var fourth = function () { return new Promise(function (resolve) { setTimeout(function () { resolve(1) }, 1000) }) } first().then(function (message) { console.log(message) sum += message return second() }).then(function (message) { console.log(message) sum += message return third() }).then(function (message) { console.log(message) sum += message return fourth() }).then(function (message) { console.log(message) sum += message console.log(sum) }).catch(console.error)
|
除了使用ES6原生支援的Promise物件外
在不支援Promise的環境下,還有很多可以替代的套件
- Q
- jQuery Deferred Object
- bluebird
- when
- RSVP
- mmDeferred
延伸資料: Promise的規格
- Promise A+
- CommonJS Promises/A
是沒有callback hell了,但是程式碼一堆then
,也不是很容易看懂
有沒有更簡單的寫法?
先談ES6 Generator與yield
用*
modifier標注function
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| function *gen() { console.log('start') yield "first" yield "second" } var g = gen() console.log(g) var a = g.next() console.log(a) var a = g.next() console.log(a) a = g.next() console.log(a)
|
跟Promise合作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| var sum = 0 var first = function () { return new Promise(function (resolve) { setTimeout(function () { resolve(1) }, 1000) }) } var second = function () { return new Promise(function (resolve) { setTimeout(function () { resolve(1) }, 1000) }) } var third = function () { return new Promise(function (resolve) { setTimeout(function () { resolve(1) }, 1000) }) } var fourth = function () { return new Promise(function (resolve) { setTimeout(function () { resolve(1) }, 1000) }) } function *gen () { yield first() yield second() yield third() yield fourth() } let g = gen() function fetch(result) { if(result.done) { console.log(sum) } else { result.value.then(function (message) { console.log(message) sum += message fetch(g.next()) }) } } fetch(g.next())
|
這樣看起來其實語法上並沒有比較簡潔
但有其他利用generator做flow control的套件
ES7 Async Await
Babel以及Node.js 7.6以上版本支援
先談generator和yield是要說明async/await的原理
async/await是generator, yield和promise的語法糖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| var sum = 0 var first = function () { return new Promise(function (resolve) { setTimeout(function () { resolve(1) }, 1000) }) } var second = function () { return new Promise(function (resolve) { setTimeout(function () { resolve(1) }, 1000) }) } var third = function () { return new Promise(function (resolve) { setTimeout(function () { resolve(1) }, 1000) }) } var fourth = function () { return new Promise(function (resolve) { setTimeout(function () { resolve(1) }, 1000) }) } var fetchAll = async function () { let a = await first() console.log(a) sum += a let b = await second() console.log(b) sum += b let c = await third() console.log(c) sum += c let d = await fourth() console.log(d) sum += d console.log(sum) } fetchAll()
|
處理併發請求
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| var first = function () { return new Promise(function (resolve) { setTimeout(function () { resolve(1) }, 1000) }) } var second = function () { return new Promise(function (resolve) { setTimeout(function () { resolve(1) }, 1000) }) } var third = function () { return new Promise(function (resolve) { setTimeout(function () { resolve(1) }, 1000) }) } var fourth = function () { return new Promise(function (resolve) { setTimeout(function () { resolve(1) }, 1000) }) } Promise.all([first(), second(), third(), fourth()]) .then(function (result) { console.log(result) let sum = result.reduce((cur, next) => cur + next) console.log(sum) }).catch(console.error) async function fetchAll () { try { let result = await Promise.all([first(), second(), third(), fourth()]) console.log(result) let sum = result.reduce((cur, next) => cur + next) console.log(sum) } catch (e) { console.log(e) } } fetchAll()
|