摘要:隨著前端的發展,異步這個詞真是越來越常見了。真正帶來革命性改變的是規范。借助,我們可以這樣完成異步任務好棒寫起來像同步處理的函數一樣別著急,少年。總結以上就是筆者總結的幾種異步編程模式。
隨著前端的發展,異步這個詞真是越來越常見了。假設我們現在有這么一個異步任務:
向服務器發起數次請求,每次請求的結果作為下次請求的參數。
來看看我們都有哪些處理方法:
Callbacks最先想到也是最常用的便是回調函數了,我們來進行簡單的封裝:
javascriptlet makeAjaxCall = (url, cb) => { // do some ajax // callback with result } makeAjaxCall("http://url1", (result) => { result = JSON.parse(result) })
嗯,看起來還不錯!但是當我們嘗試嵌套多個任務時,代碼看起來會是這樣的:
javascriptmakeAjaxCall("http://url1", (result) => { result = JSON.parse(result) makeAjaxCall(`http://url2?q=${result.query}`, (result) => { result = JSON.parse(result) makeAjaxCall(`http://url3?q=${result.query}`, (result) => { // ... }) }) })
天哪!快讓那堆 }) 見鬼去吧!
于是,我們想嘗試借助 JavaScript 事件模型:
Pub/Sub在 DOM 事件的處理中,Pub/Sub 是一種很常見的機制,比如我們要為元素加上事件監聽:
javascriptelem.addEventListener(type, (evt) => { // handler })
所以我們是不是也可以構造一個類似的模型來處理異步任務呢?
首先是要構建一個分發中心,并添加 on / emit 方法:
javascriptlet PubSub = { events: {}, on(type, handler) { let events = this.events events[type] = events[type] || [] events[type].push(handler) }, emit(type, ...datas) { let events = this.events if (!events[type]) { return } events[type].forEach((handler) => handler(...datas)) } }
然后我們便可以這樣使用:
javascriptconst urls = [ "http://url1", "http://url2", "http://url3" ] let makeAjaxCall = (url) => { // do some ajax PubSub.emit("ajaxEnd", result) } let subscribe = (urls) => { let index = 0 PubSub.on("ajaxEnd", (result) => { result = JSON.parse(result) if (urls[++index]) { makeAjaxCall(`${urls[index]}?q=${result.query}`) } }) makeAjaxCall(urls[0]) }
嗯……比起回調函數好像沒有什么革命性的改變,但是這樣做的好處是:我們可以將請求和處理函數放在不同的模塊中,減少耦合。
Promise真正帶來革命性改變的是 Promise 規范[1]。借助 Promise,我們可以這樣完成異步任務:
javascriptlet makeAjaxCall = (url) => { return new Promise((resolve, reject) => { // do some ajax resolve(result) }) } makeAjaxCall("http://url1") .then(JSON.parse) .then((result) => makeAjaxCall(`http://url2?q=${result.query}`)) .then(JSON.parse) .then((result) => makeAjaxCall(`http://url3?q=${result.query}`))
好棒!寫起來像同步處理的函數一樣!
別著急,少年。我們還有更棒的:
GeneratorsES6 的另外一個大殺器便是 Generators[2]。在一個 generator function 中,我們可以通過 yield 語句來中斷函數的執行,并在函數外部通過 next 方法來迭代語句,更重要的是我們可以通過 next 方法向函數內部注入數據,動態改變函數的行為。比如:
javascriptfunction* gen() { let a = yield 1 let b = yield a * 2 return b } let it = gen() it.next() // output: {value: 1, done: false} it.next(10) // a = 10, output: {value: 20, done: false} it.next(100) // b = 100, output: {value: 100, done: true}
通過 generator 將我們之前的 makeAjaxCall 函數進行封裝:
javascriptlet makeAjaxCall = (url) => { // do some ajax iterator.next(result) } function* requests() { let result = yield makeAjaxCall("http://url1") result = JSON.parse(result) result = yield makeAjaxCall(`http://url2?q=${result.query}`) result = JSON.parse(result) result = yield makeAjaxCall(`http://url3?q=${result.query}`) } let iterator = requests() iterator.next() // get everything start
哦!看起來邏輯很清楚的樣子,但是每次都得從外部注入 iterator 感覺好不舒服……
別急,我們讓 Promise 和 Generator 混合一下,看會產出什么黑魔法:
javascriptlet makeAjaxCall = (url) => { return new Promise((resolve, reject) => { // do some ajax resolve(result) }) } let runGen = (gen) => { let it = gen() let continuer = (value, err) => { let ret try { ret = err ? it.throw(err) : it.next(value) } catch (e) { return Promise.reject(e) } if (ret.done) { return ret.value } return Promise .resolve(ret.value) .then(continuer) .catch((e) => continuer(null, e)) } return continuer() } function* requests() { let result = yield makeAjaxCall("http://url1") result = JSON.parse(result) result = yield makeAjaxCall(`http://url2?q=${result.query}`) result = JSON.parse(result) result = yield makeAjaxCall(`http://url3?q=${result.query}`) } runGen(requests)
runGen 函數看起來像個自動機一樣,好厲害!
實際上,這個 runGen 的方法是對 ECMAScript 7 async function 的一個實現:
async functionES7 中,引入了一個更自然的特性 async function[3]。利用 async function 我們可以這樣完成任務:
javascriptlet makeAjaxCall = (url) => { return new Promise((resolve, reject) => { // do some ajax resolve(result) }) } ;(async () => { let result = await makeAjaxCall("http://url1") result = JSON.parse(result) result = await makeAjaxCall(`http://url2?q=${result.query}`) result = JSON.parse(result) result = await makeAjaxCall(`http://url3?q=${result.query}`) })()
就像我們在上文把 Promise 和 Generator 結合在一起時一樣,await 關鍵字后同樣接受一個 Promise。在 async function 中,只有在 await 后的語句完成后剩下的語句才會被執行,整個過程就像我們用 runGen 函數封裝 Generator 一樣。
總結以上就是筆者總結的幾種 JavaScript 異步編程模式。在行文過程中,我們只是簡單描述了這幾種模式,并沒有提及錯誤處理的過程,您要是對此感興趣,可以參考下文列出的引用文章。
(全文完)
參考資料Promises/A+ Specification
Going Async With ES6 Generators
ES7 async functions
Simplifying Asynchronous Coding with ES7 Async Functions
從第三方實現看 Promise
重編自我的博客,原文地址:https://idiotwu.me/going-async-with-javascript/
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/85736.html
摘要:應用日益復雜,模塊化已經成為一個迫切需求。異步模塊加載機制。引用的資源列表太長,懶得回調函數中寫一一對應的相關參數假定這里引用的資源有數十個,回調函數的參數必定非常多這就是傳說中的 簡述 緣起 模塊通常是指編程語言所提供的代碼組織機制,利用此機制可將程序拆解為獨立且通用的代碼單元。 模塊化主要是解決代碼分割、作用域隔離、模塊之間的依賴管理以及發布到生產環境時的自動化打包與處理等多個方面...
摘要:從最開始的到封裝后的都在試圖解決異步編程過程中的問題。為了讓編程更美好,我們就需要引入來降低異步編程的復雜性。異步編程入門的全稱是前端經典面試題從輸入到頁面加載發生了什么這是一篇開發的科普類文章,涉及到優化等多個方面。 TypeScript 入門教程 從 JavaScript 程序員的角度總結思考,循序漸進的理解 TypeScript。 網絡基礎知識之 HTTP 協議 詳細介紹 HTT...
摘要:提出標準,允許腳本創建多個線程,但是子線程完全受主線程控制,且不得操作。所以,這個新標準并沒有改變單線程的本質。事件循環主線程線程只會做一件事,就是從消息隊列里面取消息執行消息,再取消息再執行。工作線程是生產者,主線程是消費者。 最近項目中遇到了一個場景,其實很常見,就是定時獲取接口刷新數據。那么問題來了,假設我設置的定時時間為1s,而數據接口返回大于1s,應該用同步阻塞還是異步?我們...
摘要:當主線程開始執行異步任務,實際就是執行對應的回調函數。異步任務必須指定回調函數。所以注意的是,只是將事件插入了任務隊列,必須等到當前代碼執行棧執行完,主線程才會去執行它指定的回調函數。 最近本人對于js的運行機制,特別是異步,還有回調函數感覺很亂,于是參考了很多有用的博客(博客原文地址會在文末給出),整理如下: js單線程 我們都知道,Javascript語言的執行環境是單線程(si...
摘要:曾經的理解首先,是單線程語言,也就意味著同一個時間只能做一件事,那么為什么不是多線程呢這樣還能提高效率啊假定同時有兩個線程,一個線程在某個節點上編輯了內容,而另一個線程刪除了這個節點,這時瀏覽器就很懵逼了,到底以執行哪個操作呢所以,設計者把 Event Loop曾經的理解 首先,JS是單線程語言,也就意味著同一個時間只能做一件事,那么 為什么JavaScript不是多線程呢?這樣還能提...
閱讀 3920·2021-11-24 09:38
閱讀 3096·2021-11-17 09:33
閱讀 3871·2021-11-10 11:48
閱讀 1241·2021-10-14 09:48
閱讀 3130·2019-08-30 13:14
閱讀 2551·2019-08-29 18:37
閱讀 3393·2019-08-29 12:38
閱讀 1418·2019-08-29 12:30