新手如何快速理解js異步編程
前言
異步編程從早期的 callback、事件發(fā)布\訂閱模式到 ES6 的 Promise、Generator 在到 ES2017 中 async,看似風(fēng)格迥異,但是還是有一條暗線(xiàn)將它們串聯(lián)在一起的,就是希望將異步編程的代碼表達(dá)盡量地貼合自然語(yǔ)言的線(xiàn)性思維。
以這條暗線(xiàn)將上述幾種解決方案連在一起,就可以更好地理解異步編程的原理、魅力。
├── 事件發(fā)布\訂閱模式 <= Callback
├── Promise <= 事件發(fā)布\訂閱模式
├── Async、Await <= Promise、Generator
事件發(fā)布\訂閱模式 <= Callback
這個(gè)模式本質(zhì)上就是回調(diào)函數(shù)的事件化。它本身并無(wú)同步、異步調(diào)用的問(wèn)題,我們只是使用它來(lái)實(shí)現(xiàn)事件與回調(diào)函數(shù)之間的關(guān)聯(lián)。比較典型的有 NodeJS 的 events 模塊
const { EventEmitter } = require('events') const eventEmitter = new EventEmitter() // 訂閱 eventEmitter.on("event", function(msg) { console.log("event", msg) }) // 發(fā)布 eventEmitter.emit("event", "Hello world")
那么這種模式是如何與 Callback 關(guān)聯(lián)的呢?我們可以利用 Javascript 簡(jiǎn)單實(shí)現(xiàn) EventEmitter,答案就顯而易見(jiàn)了。
class usrEventEmitter { constructor () { this.listeners = {} } // 訂閱,callback 為每個(gè) event 的偵聽(tīng)器 on(eventName, callback) { if (!this.listeners[eventName]) this.listeners[eventName] = [] this.listeners[eventName].push(callback) } // 發(fā)布 emit(eventName, params) { this.listeners[eventName].forEach(callback => { callback(params) }) } // 注銷(xiāo) off(eventName, callback) { const rest = this.listeners[eventName].fitler(elem => elem !== callback) this.listeners[eventName] = rest } // 訂閱一次 once(eventName, callback) { const handler = function() { callback() this.off(eventName, handler) } this.on(eventName, handler) } }
上述實(shí)現(xiàn)忽略了很多細(xì)節(jié),例如異常處理、多參數(shù)傳遞等。只是為了展示事件訂閱\發(fā)布模式。
很明顯的看出,我們使用這種設(shè)計(jì)模式對(duì)異步編程做了邏輯上的分離,將其語(yǔ)義化為
// 一些事件可能會(huì)被觸發(fā) eventEmitter.on // 當(dāng)它發(fā)生的時(shí)候,要這樣處理 eventEmitter.emit
也就是說(shuō),我們將最初的 Callback 變成了事件監(jiān)聽(tīng)器,從而優(yōu)雅地解決異步編程。
Promise <= 事件發(fā)布\訂閱模式
使用事件發(fā)布\訂閱模式時(shí),需要我們事先嚴(yán)謹(jǐn)?shù)卦O(shè)置目標(biāo),也就是上面所說(shuō)的,必須要縝密地設(shè)定好有哪些事件會(huì)發(fā)生。這與我們語(yǔ)言的線(xiàn)性思維很違和。那么有沒(méi)有一種方式可以解決這個(gè)問(wèn)題,社區(qū)產(chǎn)出了 Promise。
const promise = new Promise(function(resolve, reject) {
try {
setTimeout(() => {
resolve('hello world')
}, 500)
} catch (error) {
reject(error)
}
})
// 語(yǔ)義就變?yōu)橄劝l(fā)生一些異步行為,then 我們應(yīng)該這么處理 promise.then(msg => console.log(msg)).catch(error => console.log('err', error))
那么這種 Promise 與事件發(fā)布\訂閱模式有什么聯(lián)系呢?我們可以利用 EventEmitter 來(lái)實(shí)現(xiàn) Promise,這樣可能會(huì)對(duì)你有所啟發(fā)。
我們可以將 Promise 視為一個(gè) EventEmitter,它包含了 { state: 'pending' } 來(lái)描述當(dāng)前的狀態(tài),同時(shí)偵聽(tīng)它的變化
- 當(dāng)成功時(shí) { state: 'fulfilled' },要做些什么 on('resolve', callback);
- 當(dāng)失敗時(shí) { state: 'rejected' },要做些什么 on('reject', callback)。
具體實(shí)現(xiàn)如下
const { EventEmitter } = require('events') class usrPromise extends EventEmitter { // 構(gòu)造時(shí)候執(zhí)行 constructor(executor) { super() // 發(fā)布 const resolve = (value) => this.emit('resolve', value) const reject = (reason) => this.emit('reject', reason) if (executor) { // 模擬 event loop,注此處利用 Macrotask 來(lái)模擬 Microtask setTimeout(() => executor(resolve, reject)) } } then(resolveHandler, rejectHandler) { const nextPromise = new usrPromise() // 訂閱 resolve 事件 if (resolveHandler) { const resolve = (data) => { const result = resolveHandler(data) nextPromise.emit('resolve', result) } this.on('resolve', resolve) } // 訂閱 reject 事件 if (rejectHandler) { const reject = (data) => { const result = rejectHandler(data) nextPromise.emit('reject', result) } this.on('reject', reject) } else { this.on('reject', (data) => { promise.emit('reject', data) }) } return nextPromise } catch(handler) { this.on('reject', handler) } }
我們使用 then 方法來(lái)將預(yù)先需要定義的事件偵聽(tīng)器存放起來(lái),同時(shí)在 executor 中設(shè)定這些事件該在什么時(shí)候?qū)嵭小?br />
可以看出從事件發(fā)布\訂閱模式到 Promise,帶來(lái)了語(yǔ)義上的巨大變革,但是還是需要使用 new Promise 來(lái)描述整個(gè)狀態(tài)的轉(zhuǎn)換,那么有沒(méi)有更好地實(shí)現(xiàn)方式呢?
async、await <= Promise、Generator
async、await 標(biāo)準(zhǔn)是 ES 2017 引入,提供一種更加簡(jiǎn)潔的異步解決方案。
async function say(greeting) { return new Promise(function(resolve, then) { setTimeout(function() { resolve(greeting) }, 1500) }) } ;(async function() { let v1 = await say('Hello') console.log(v1) let v2 = await say('World') console.log(v2) })()
await 可以理解為暫停當(dāng)前 async function 的執(zhí)行,等待 Promise 處理完成。。若 Promise 正常處理(fulfilled),其回調(diào)的resolve函數(shù)參數(shù)作為 await 表達(dá)式的值。
async、await 的出現(xiàn),減少了多個(gè) then 的鏈?zhǔn)秸{(diào)用形式的代碼。下面我們結(jié)合 Promise 與 Generator 來(lái)實(shí)現(xiàn) async、await
function async(makeGenerator) { return function() { const generator = makeGenerator.apply(this, arguments) function handle({ value, done }) { if (done === true) return Promise.resolve(value) return Promise.resolve(value).then( (res) => { return handle(generator.next(res)) }, function(err) { return handle(generator.throw(err)) } ) } try { return handle(generator.next()) } catch (ex) { return Promise.reject(ex) } } } async(function*() { var v1 = yield say('hello') console.log(1, v1) var v2 = yield say('world') console.log(2, v2) })()
本質(zhì)上就是利用遞歸完成 function* () { ... } 的自動(dòng)執(zhí)行。相比與 Generator 函數(shù),這種形式無(wú)需手動(dòng)執(zhí)行,并且具有更好的語(yǔ)義。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,
相關(guān)文章
解決js函數(shù)閉包內(nèi)存泄露問(wèn)題的辦法
這篇文章主要通過(guò)舉例介紹了解決js函數(shù)閉包內(nèi)存泄露問(wèn)題的辦法,感興趣的小伙伴們可以參考一下2016-01-01深入理解在JS中通過(guò)四種設(shè)置事件處理程序的方法
所有的JavaScript事件處理程序的作用域是在其定義時(shí)的作用域而非調(diào)用時(shí)的作用域中執(zhí)行,并且它們能存取那個(gè)作用域中的任何一個(gè)本地變量。但是HTML標(biāo)簽屬性注冊(cè)處理程序就是一個(gè)例外??聪旅嫠姆N方式2017-03-03TextArea設(shè)置MaxLength屬性最大輸入值的js代碼
TextArea中限制最大輸入長(zhǎng)度,實(shí)現(xiàn)的方法種種,我們不在一一介紹,今天本文推薦一種簡(jiǎn)單實(shí)用的方法,需要的朋友可以參考下2012-12-12微信小程序完美解決scroll-view高度自適應(yīng)問(wèn)題的方法
這篇文章主要介紹了微信小程序完美解決scroll-view高度自適應(yīng)問(wèn)題的方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08js實(shí)現(xiàn)Select頭像選擇實(shí)時(shí)預(yù)覽代碼
這篇文章主要介紹了js實(shí)現(xiàn)Select頭像選擇實(shí)時(shí)預(yù)覽代碼,涉及javascript動(dòng)態(tài)遍歷及設(shè)置select選項(xiàng)的技巧,非常簡(jiǎn)單實(shí)用,需要的朋友可以參考下2015-08-08javascript中encodeURI和decodeURI方法使用介紹
encodeURI和decodeURI是成對(duì)來(lái)使用的,因?yàn)闉g覽器的地址欄有中文字符的話(huà),可以會(huì)出現(xiàn)不可預(yù)期的錯(cuò)誤,所以可以encodeURI把非英文字符轉(zhuǎn)化為英文編碼,decodeURI可以用來(lái)把字符還原回來(lái)2013-05-05使用window.postMessage()方法在兩個(gè)網(wǎng)頁(yè)間傳遞數(shù)據(jù)
這篇文章介紹了使用window.postMessage()在兩個(gè)網(wǎng)頁(yè)間傳遞數(shù)據(jù)的方法,文中通過(guò)示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-06-06