介紹關於js開發中所涉及的主流異步編程解決方案
repo: async-for-js
例子
插入3個div元素,其中第二個div元素使用setTimeout
模擬異步操作,理想的插入順序爲div1 div2 div3,但這裏的代碼的插入順序爲div1 div3 div2。
// async way function _async() { document.body.appendChild(div1) setTimeout(function () { document.body.appendChild(div2) }, 2000) document.body.appendChild(div3) } _async()
Callback
最常用的方法是利用callback
(回調函數)的方式,因爲js中函數也是作爲對象存在的,因此可以被當做參數傳入另一個函數中,只需要在異步操作執行代碼後調用回調函數即可。
但是使用回調函數有很明顯的侷限性,一方面體現在需要自己對異步操作進行控制,另一方面還很容易陷入”回調地獄”。
// use plain callback to sync function _callback(cb) { document.body.appendChild(div1) setTimeout(function () { document.body.appendChild(div2) cb('done') }, 2000) return 'done' } _callback(function () { document.body.appendChild(div3) })
Promise
因爲回調地獄的問題,後來聰明的人使用將回調延遲執行的思想,從而發明了promise庫,調用者可以根據異步流程隨心所欲的resolve或reject某個值給之後的操作,從而解決了毀掉地獄的問題。
不過使用promise仍然有問題,就是當代碼邏輯很長的時候,總需要帶着大片大片的then
方法,可讀性仍然不夠清晰。
// use promise to sync function _promise() { document.body.appendChild(div1) return new Promise(res => { setTimeout(function () { document.body.appendChild(div2) res('done') }, 2000) }) } _promise().then(data => { console.log(data) document.body.appendChild(div3) })
Generate
後來promise加入了es6標準,同時推出了新的異步解決方案,叫做generate函數,大體講是提供了一個具有狀態機功能的函數,每次執行會停止在實現者聲明的某個狀態,下次調用會繼續從這個狀態開始執行。
generate的出現,使必須依靠callback
實現異步操作的代碼風格,可以使用同步代碼風格實現,是一顆非常甜的語法糖。
但是它仍有有一些缺點,就是它作爲狀態機,無法自執行,必須藉助實現一個run函數或使用第三方庫(如co
)。
// use generate to sync function* _generate() { document.body.appendChild(div1) yield function (cb) { setTimeout(function () { document.body.appendChild(div2) cb() }, 2000) } document.body.appendChild(div3) return 'done' } function run(fn) { var gen = fn() function next(data) { var result = gen.next(data) console.log(result.value) if (result.done) return result.value(next) } next() } run(_generate)
Async/await
爲了解決generate的缺點,es7很快發佈了繼generate更強大的一個東西,叫做async函數。簡單說,它並沒有什麼新特性,把它看做是可以自執行的generate函數即可,其中的await的操作符可以看做是yield操作符的翻版。
// use async/await and promise to sync const fn = function () { return new Promise(res => { setTimeout(function () { res(document.body.appendChild(div2)) }, 2000) }) } async function _await () { document.body.appendChild(div1) const f = await fn() console.log(f) document.body.appendChild(div3) } _await()
Observable
最近很火的rxjs也快成用來解決這個問題,詳細的介紹可以去它的官網瞭解。
// use rxjs and callback to sync const _callbackObservable = Observable.bindCallback(_callback) const result = _callbackObservable() // result.subscribe(x => { // document.body.appendChild(div3) // console.log(x) // })