摘要:第二種則一定會(huì)執(zhí)行所有的異步函數(shù),即便你需要使用的是這些高階函數(shù)。并發(fā)實(shí)現(xiàn)的異步數(shù)組然后修改,使用即可上面的其他內(nèi)容終結(jié)整個(gè)鏈?zhǔn)讲僮鞑⒎祷亟Y(jié)果這里使用是為了兼容的調(diào)用方式調(diào)用方式不變。
JavaScript 異步數(shù)組
吾輩的博客原文: https://blog.rxliuli.com/p/5e...場(chǎng)景
吾輩是一只在飛向太陽(yáng)的螢火蟲(chóng)
JavaScript 中的數(shù)組是一個(gè)相當(dāng)泛用性的數(shù)據(jù)結(jié)構(gòu),能當(dāng)數(shù)組,元組,隊(duì)列,棧進(jìn)行操作,更好的是 JavaScript 提供了很多原生的高階函數(shù),便于我們對(duì)數(shù)組整體操作。
然而,JavaScript 中的高階函數(shù)仍有缺陷 -- 異步!當(dāng)你把它們放在一起使用時(shí),就會(huì)感覺(jué)到這種問(wèn)題的所在。
例如現(xiàn)在,有一組 id,我們要根據(jù) id 獲取到遠(yuǎn)端服務(wù)器 id 對(duì)應(yīng)的值,然后將之打印出來(lái)。那么,我們要怎么做呢?
const wait = ms => new Promise(resolve => setTimeout(resolve, ms)) async function get(id) { // 這里只是為了模擬每個(gè)請(qǐng)求的時(shí)間可能是不定的 await wait(Math.random() * id * 100) return "內(nèi)容: " + id.toString() } const ids = [1, 2, 3, 4]
你或許會(huì)下意識(shí)地寫(xiě)出下面的代碼
ids.forEach(async id => console.log(await get(id)))
事實(shí)上,控制臺(tái)輸出是無(wú)序的,而并非想象中的 1, 2, 3, 4 依次輸出
內(nèi)容: 2 ????? 內(nèi)容: 3 ????? 內(nèi)容: 1 ????? 內(nèi)容: 4
這是為什么呢?原因便是 JavaScript 中數(shù)組的高階函數(shù)并不會(huì)等待異步函數(shù)的返回!當(dāng)你在網(wǎng)絡(luò)上搜索時(shí),會(huì)發(fā)現(xiàn)很多人會(huì)說(shuō)可以使用 for-of, for-in 解決這個(gè)問(wèn)題。
;(async () => { for (let id of ids) { console.log(await get(id)) } })()
或者,使用 Promise.all 也是一種解決方案
;(async () => { ;(await Promise.all(ids.map(get))).forEach(v => console.log(v)) })()
然而,第一種方式相當(dāng)于丟棄了 Array 的所有高階函數(shù),再次重返遠(yuǎn)古 for 循環(huán)時(shí)代了。第二種則一定會(huì)執(zhí)行所有的異步函數(shù),即便你需要使用的是 find/findIndex/some/every 這些高階函數(shù)。那么,有沒(méi)有更好的解決方案呢?
思考既然原生的 Array 不支持完善的異步操作,那么,為什么不由我們來(lái)實(shí)現(xiàn)一個(gè)呢?
實(shí)現(xiàn)思路:
創(chuàng)建異步數(shù)組類型 AsyncArray
內(nèi)置一個(gè)數(shù)組保存當(dāng)前異步操作數(shù)組的值
實(shí)現(xiàn)數(shù)組的高階函數(shù)并實(shí)現(xiàn)支持異步函數(shù)順序執(zhí)行
獲取到內(nèi)置的數(shù)組
class AsyncArray { constructor(...args) { this._arr = Array.from(args) this._task = [] } async forEach(fn) { const arr = this._arr for (let i = 0, len = arr.length; i < len; i++) { await fn(arr[i], i, this) } } } new AsyncArray(...ids).forEach(async id => console.log(await get(id)))
打印結(jié)果確實(shí)有順序了,看似一切很美好?
然而,當(dāng)我們?cè)賹?shí)現(xiàn)一個(gè) map 試一下
class AsyncArray { constructor(...args) { this._arr = Array.from(args) } async forEach(fn) { const arr = this._arr for (let i = 0, len = arr.length; i < len; i++) { await fn(arr[i], i, this) } } async map(fn) { const arr = this._arr const res = [] for (let i = 0, len = arr.length; i < len; i++) { res.push(await fn(arr[i], i, this)) } return this } }
調(diào)用一下
new AsyncArray(...ids).map(get).forEach(async res => console.log(res)) // 拋出錯(cuò)誤 // (intermediate value).map(...).forEach is not a function
然而會(huì)有問(wèn)題,實(shí)際上 map 返回的是 Promise,所以我們還必須使用 await 進(jìn)行等待
;(async () => { ;(await new AsyncArray(...ids).map(get)).forEach(async res => console.log(res), ) })()
是不是感覺(jué)超級(jí)蠢?吾輩也是這樣認(rèn)為的!
鏈?zhǔn)秸{(diào)用加延遲執(zhí)行我們可以嘗試使用鏈?zhǔn)秸{(diào)用加延遲執(zhí)行修改這個(gè) AsyncArray
/** * 保存高階函數(shù)傳入的異步操作 */ class Action { constructor(type, args) { /** * @field 異步操作的類型 * @type {string} */ this.type = type /** * @field 異步操作的參數(shù)數(shù)組 * @type {Function} */ this.args = args } } /** * 所有的操作類型 */ Action.Type = { forEach: "forEach", map: "map", filter: "filter", } /** * 真正實(shí)現(xiàn)的異步數(shù)組 */ class InnerAsyncArray { constructor(arr) { this._arr = arr } async forEach(fn) { const arr = this._arr for (let i = 0, len = arr.length; i < len; i++) { await fn(arr[i], i, this) } this._arr = [] } async map(fn) { const arr = this._arr const res = [] for (let i = 0, len = arr.length; i < len; i++) { res.push(await fn(arr[i], i, this)) } this._arr = res return this } async filter(fn) { const arr = this._arr const res = [] for (let i = 0, len = arr.length; i < len; i++) { if (await fn(arr[i], i, this)) { res.push(arr[i]) } } this._arr = res return this } } class AsyncArray { constructor(...args) { this._arr = Array.from(args) /** * @field 保存異步任務(wù) * @type {Action[]} */ this._task = [] } forEach(fn) { this._task.push(new Action(Action.Type.forEach, [fn])) return this } map(fn) { this._task.push(new Action(Action.Type.map, [fn])) return this } filter(fn) { this._task.push(new Action(Action.Type.filter, [fn])) return this } /** * 終結(jié)整個(gè)鏈?zhǔn)讲僮鞑⒎祷亟Y(jié)果 */ async value() { const arr = new InnerAsyncArray(this._arr) let result for (let task of this._task) { result = await arr[task.type](...task.args) } return result } }
使用一下
new AsyncArray(...ids) .filter(async i => i % 2 === 0) .map(get) .forEach(async res => console.log(res)) .value()
可以看到,確實(shí)符合預(yù)期了,然而每次都要調(diào)用 value(),終歸有些麻煩。
使用 then 以支持 await 自動(dòng)結(jié)束這里使用 then() 替代它以使得可以使用 await 自動(dòng)計(jì)算結(jié)果
class AsyncArray { // 上面的其他內(nèi)容... /** * 終結(jié)整個(gè)鏈?zhǔn)讲僮鞑⒎祷亟Y(jié)果 */ async then(resolve) { const arr = new InnerAsyncArray(this._arr) let result for (let task of this._task) { result = await arr[task.type](...task.args) } // 這里使用 resolve(result) 是為了兼容 await 的調(diào)用方式 resolve(result) return result } }
現(xiàn)在,可以使用 await 結(jié)束這次鏈?zhǔn)秸{(diào)用了
await new AsyncArray(...ids).map(get).forEach(async res => console.log(res))
突然之間,我們發(fā)現(xiàn)了一個(gè)問(wèn)題,為什么會(huì)這么慢?一個(gè)個(gè)去進(jìn)行異步操作太慢了,難道就不能一次性全部發(fā)送出去,然后有序的處理結(jié)果就好了嘛?
并發(fā)異步操作我們可以使用 Promise.all 并發(fā)執(zhí)行異步操作,然后對(duì)它們的結(jié)果進(jìn)行有序地處理。
/** * 并發(fā)實(shí)現(xiàn)的異步數(shù)組 */ class InnerAsyncArrayParallel { constructor(arr) { this._arr = arr } async _all(fn) { return Promise.all(this._arr.map(fn)) } async forEach(fn) { await this._all(fn) this._arr = [] } async map(fn) { this._arr = await this._all(fn) return this } async filter(fn) { const arr = await this._all(fn) this._arr = this._arr.filter((v, i) => arr[i]) return this } }
然后修改 AsyncArray,使用 _AsyncArrayParallel 即可
class AsyncArray { // 上面的其他內(nèi)容... /** * 終結(jié)整個(gè)鏈?zhǔn)讲僮鞑⒎祷亟Y(jié)果 */ async then(resolve) { const arr = new InnerAsyncArrayParallel(this._arr) let result = this._arr for (let task of this._task) { result = await arr[task.type](...task.args) } // 這里使用 resolve(result) 是為了兼容 await 的調(diào)用方式 if (resolve) { resolve(result) } return result } }
調(diào)用方式不變。當(dāng)然,由于使用 Promise.all 實(shí)現(xiàn),也同樣受到它的限制 -- 異步操作實(shí)際上全部執(zhí)行了。
串行/并行相互轉(zhuǎn)換現(xiàn)在我們的 _AsyncArray 和 _AsyncArrayParallel 兩個(gè)類只能二選一,所以,我們需要添加兩個(gè)函數(shù)用于互相轉(zhuǎn)換。
class AsyncArray { constructor(...args) { this._arr = Array.from(args) /** * @field 保存異步任務(wù) * @type {AsyncArrayAction[]} */ this._task = [] /** * 是否并行化 */ this._parallel = false } // 其他內(nèi)容... parallel() { this._parallel = true return this } serial() { this._parallel = false return this } async then() { const arr = this._parallel ? new InnerAsyncArrayParallel(this._arr) : new InnerAsyncArray(this._arr) let result = this._arr for (let task of this._task) { result = await arr[task.type](...task.args) } if (resolve) { resolve(result) } return result } }
現(xiàn)在,我們可以在真正執(zhí)行之前在任意位置對(duì)其進(jìn)行轉(zhuǎn)換了
await new AsyncArray(...ids) .parallel() .filter(async i => i % 2 === 0) .map(get) .forEach(async res => console.log(res))并發(fā)執(zhí)行多個(gè)異步操作
然而,上面的代碼有一些隱藏的問(wèn)題
await 之后返回值不是一個(gè)數(shù)組
;(async () => { const asyncArray = new AsyncArray(...ids) console.log(await asyncArray.map(i => i * 2)) // InnerAsyncArray { _arr: [ 2, 4, 6, 8 ] } })()
上面的 map, filter 調(diào)用在 await 之后仍會(huì)影響到下面的調(diào)用
;(async () => { const asyncArray = new AsyncArray(...ids) console.log(await asyncArray.map(i => i * 2)) // InnerAsyncArray { _arr: [ 2, 4, 6, 8 ] } console.log(await asyncArray) // InnerAsyncArray { _arr: [ 2, 4, 6, 8 ] } })()
并發(fā)調(diào)用的順序不能確定,會(huì)影響到內(nèi)部數(shù)組,導(dǎo)致結(jié)果不能確定
;(async () => { const asyncArray = new AsyncArray(...ids) ;(async () => { console.log( await asyncArray.filter(async i => i % 2 === 1).map(async i => i * 2), ) // InnerAsyncArray { _arr: [ 2, 6 ] } })() ;(async () => { console.log(await asyncArray) // InnerAsyncArray { _arr: [ 2, 6 ] } })() })()
先解決第一個(gè)問(wèn)題,這里只需要判斷一下是否為終結(jié)操作(forEach),是的話就直接返回結(jié)果,否則繼續(xù)下一次循環(huán)
class AsyncArray { // 其他內(nèi)容... async then(resolve, reject) { const arr = this._parallel ? new InnerAsyncArrayParallel(this._arr) : new InnerAsyncArray(this._arr) let result = this._arr for (let task of this._task) { const temp = await arr[task.type](...task.args) if ( temp instanceof InnerAsyncArray || temp instanceof InnerAsyncArrayParallel ) { result = temp._arr } else { // 如果已經(jīng)是終結(jié)操作就返回?cái)?shù)組的值 if (resolve) { resolve(temp) } return temp } } if (resolve) { resolve(result) } return result } }
現(xiàn)在,第一個(gè)問(wèn)題簡(jiǎn)單解決
;(async () => { const asyncArray = new AsyncArray(...ids) console.log(await asyncArray.map(i => i * 2)) // [ 2, 4, 6, 8 ] })()
第二、第三個(gè)問(wèn)題看起來(lái)似乎是同一個(gè)問(wèn)題?其實(shí)我們可以按照常規(guī)思維解決第一個(gè)問(wèn)題。既然 await 之后仍然會(huì)影響到下面的調(diào)用,那就在 then 中把 _task 清空好了,修改 then 函數(shù)
class AsyncArray { // 其他內(nèi)容... async then(resolve, reject) { const arr = this._parallel ? new InnerAsyncArrayParallel(this._arr) : new InnerAsyncArray(this._arr) let result = this._arr for (let task of this._task) { const temp = await arr[task.type](...task.args) if ( temp instanceof InnerAsyncArray || temp instanceof InnerAsyncArrayParallel ) { result = temp._arr } else { // 如果已經(jīng)是終結(jié)操作就返回?cái)?shù)組的值 if (resolve) { resolve(temp) } this._task = [] return temp } } if (resolve) { resolve(result) } this._task = [] return result } }
現(xiàn)在,第一個(gè)問(wèn)題解決了,但第二個(gè)問(wèn)題不會(huì)解決。究其原因,還是異步事件隊(duì)列的問(wèn)題,雖然 async-await 能夠讓我們以同步的方式寫(xiě)異步的代碼,但千萬(wàn)不可忘記它們本質(zhì)上還是異步的!
;(async () => { await Promise.all([ (async () => { console.log( await asyncArray.filter(async i => i % 2 === 1).map(async i => i * 2), ) // [ 2, 6 ] })(), (async () => { console.log(await asyncArray) // [ 2, 6 ] })(), ]) console.log(await asyncArray) // [ 1, 2, 3, 4 ] })()
可以看到,在使用 await 進(jìn)行等待之后就如同預(yù)期的 _task 被清空了。然而,并發(fā)執(zhí)行的沒(méi)有等待的 await asyncArray 卻有奇怪的問(wèn)題,因?yàn)樗窃?_task 清空之前執(zhí)行的。
并且,這帶來(lái)一個(gè)副作用: 無(wú)法緩存操作了
;(async () => { const asyncArray = new AsyncArray(...ids).map(i => i * 2) console.log(await asyncArray) // [ 2, 4, 6, 8 ] console.log(await asyncArray) // [ 1, 2, 3, 4 ] })()使用不可變數(shù)據(jù)
為了解決直接修改內(nèi)部數(shù)組造成的問(wèn)題,我們可以使用不可變數(shù)據(jù)解決這個(gè)問(wèn)題。試想:如果我們每次操作都返回一個(gè)新的 AsyncArray,他們之間沒(méi)有關(guān)聯(lián),這樣又如何呢?
class AsyncArray { constructor(...args) { this._arr = Array.from(args) /** * @field 保存異步任務(wù) * @type {Action[]} */ this._task = [] /** * 是否并行化 */ this._parallel = false } forEach(fn) { return this._addTask(Action.Type.forEach, [fn]) } map(fn) { return this._addTask(Action.Type.map, [fn]) } filter(fn) { return this._addTask(Action.Type.filter, [fn]) } parallel() { this._parallel = true return this } serial() { this._parallel = false return this } _addTask(type, args) { const result = new AsyncArray(...this._arr) result._task = [...this._task, new Action(type, args)] result._parallel = this._parallel return result } /** * 終結(jié)整個(gè)鏈?zhǔn)讲僮鞑⒎祷亟Y(jié)果 */ async then(resolve, reject) { const arr = this._parallel ? new InnerAsyncArrayParallel(this._arr) : new InnerAsyncArray(this._arr) let result = this._arr for (let task of this._task) { const temp = await arr[task.type](...task.args) if ( temp instanceof InnerAsyncArray || temp instanceof InnerAsyncArrayParallel ) { result = temp._arr } else { // 如果已經(jīng)是終結(jié)操作就返回?cái)?shù)組的值 if (resolve) { resolve(temp) } return temp } } if (resolve) { resolve(result) } return result } }
再次測(cè)試上面的那第三個(gè)問(wèn)題,發(fā)現(xiàn)已經(jīng)一切正常了呢
并發(fā)調(diào)用的順序不能確定,但不會(huì)影響內(nèi)部數(shù)組了,結(jié)果是確定的
;(async () => { const asyncArray = new AsyncArray(...ids) await Promise.all([ (async () => { console.log( await asyncArray.filter(async i => i % 2 === 1).map(async i => i * 2), ) // [ 2, 6 ] })(), (async () => { console.log(await asyncArray) // [ 1, 2, 3, 4 ] })(), ]) console.log(await asyncArray) // [ 1, 2, 3, 4 ] })()
操作可以被緩存
;(async () => { const asyncArray = new AsyncArray(...ids).map(i => i * 2) console.log(await asyncArray) // [ 2, 4, 6, 8 ] console.log(await asyncArray) // [ 2, 4, 6, 8 ] })()完整代碼
下面吾輩把完整的代碼貼出來(lái)
/** * 保存高階函數(shù)傳入的異步操作 */ class Action { constructor(type, args) { /** * @field 異步操作的類型 * @type {string} */ this.type = type /** * @field 異步操作的參數(shù)數(shù)組 * @type {Function} */ this.args = args } } /** * 所有的操作類型 */ Action.Type = { forEach: "forEach", map: "map", filter: "filter", } /** * 真正實(shí)現(xiàn)的異步數(shù)組 */ class InnerAsyncArray { constructor(arr) { this._arr = arr } async forEach(fn) { const arr = this._arr for (let i = 0, len = arr.length; i < len; i++) { await fn(arr[i], i, this) } this._arr = [] } async map(fn) { const arr = this._arr const res = [] for (let i = 0, len = arr.length; i < len; i++) { res.push(await fn(arr[i], i, this)) } this._arr = res return this } async filter(fn) { const arr = this._arr const res = [] for (let i = 0, len = arr.length; i < len; i++) { if (await fn(arr[i], i, this)) { res.push(arr[i]) } } this._arr = res return this } } class InnerAsyncArrayParallel { constructor(arr) { this._arr = arr } async _all(fn) { return Promise.all(this._arr.map(fn)) } async forEach(fn) { await this._all(fn) this._arr = [] } async map(fn) { this._arr = await this._all(fn) return this } async filter(fn) { const arr = await this._all(fn) this._arr = this._arr.filter((v, i) => arr[i]) return this } } class AsyncArray { constructor(...args) { this._arr = Array.from(args) /** * @field 保存異步任務(wù) * @type {Action[]} */ this._task = [] /** * 是否并行化 */ this._parallel = false } forEach(fn) { return this._addTask(Action.Type.forEach, [fn]) } map(fn) { return this._addTask(Action.Type.map, [fn]) } filter(fn) { return this._addTask(Action.Type.filter, [fn]) } parallel() { this._parallel = true return this } serial() { this._parallel = false return this } _addTask(type, args) { const result = new AsyncArray(...this._arr) result._task = [...this._task, new Action(type, args)] result._parallel = this._parallel return result } /** * 終結(jié)整個(gè)鏈?zhǔn)讲僮鞑⒎祷亟Y(jié)果 */ async then(resolve, reject) { const arr = this._parallel ? new InnerAsyncArrayParallel(this._arr) : new InnerAsyncArray(this._arr) let result = this._arr for (let task of this._task) { const temp = await arr[task.type](...task.args) if ( temp instanceof InnerAsyncArray || temp instanceof InnerAsyncArrayParallel ) { result = temp._arr } else { // 如果已經(jīng)是終結(jié)操作就返回?cái)?shù)組的值 if (resolve) { resolve(temp) } return temp } } if (resolve) { resolve(result) } return result } }總結(jié)
那么,關(guān)于 JavaScript 中如何封裝一個(gè)可以使用異步操作高階函數(shù)的數(shù)組就先到這里了,完整的 JavaScript 異步數(shù)組請(qǐng)參考吾輩的 AsyncArray(使用 TypeScript 編寫(xiě))。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/104944.html
摘要:的執(zhí)行與狀態(tài)無(wú)關(guān)當(dāng)?shù)玫綘顟B(tài)不論成功或失敗后就會(huì)執(zhí)行,原文鏈接參考鏈接對(duì)象 同期異步系列文章推薦談一談javascript異步j(luò)avascript異步中的回調(diào)javascript異步與promisejavascript異步之Promise.resolve()、Promise.reject()javascript異步之Promise then和catchjavascript異步之a(chǎn)sync...
摘要:本次的任務(wù)假如。。。。。引擎發(fā)生了重大故障,方法變成了,為了拯救世界,需要開(kāi)發(fā)一個(gè)模塊來(lái)解決此問(wèn)題。實(shí)現(xiàn)首先要知道是什么是對(duì)異步編程的一種抽象。數(shù)組中任何一個(gè)為的話,則整個(gè)調(diào)用會(huì)立即終止,并返回一個(gè)的新的對(duì)象。 本次的任務(wù) 假如。。。。。 JavaScript v8 引擎發(fā)生了重大故障,Promise.all 方法變成了 undefined ,為了拯救 JavaScript 世界,需要...
摘要:年月日更新后來(lái)在編程過(guò)程中發(fā)現(xiàn)用會(huì)更加方便。如果是沒(méi)辦法應(yīng)對(duì)異步。重新調(diào)了一下,發(fā)現(xiàn)幾點(diǎn)寫(xiě)下來(lái)異步操作這里的回調(diào)函數(shù)一定要寫(xiě)成這樣的形式,如果使用的是這樣的形式會(huì)指向這個(gè)匿名函數(shù)。 2017年7月20日更新 后來(lái)在編程過(guò)程中發(fā)現(xiàn)用iterator會(huì)更加方便。在Array的iteration方法里面有這么一個(gè):Array.prototype[@@iterator]()。用法是`arr[S...
摘要:回調(diào)函數(shù)是的一大特色官方的基本都是以會(huì)回調(diào)方式傳遞函數(shù)返回值。針對(duì)這種普遍問(wèn)題,應(yīng)勢(shì)而生基本用法創(chuàng)建做一些異步操作的事情,然后一切正常的構(gòu)造器接受一個(gè)函數(shù)作為參數(shù),它會(huì)傳遞給這個(gè)回調(diào)函數(shù)兩個(gè)變量和。 Promise 是什么? Promise 對(duì)象用來(lái)進(jìn)行延遲(deferred) 和 異步(asynchronous) 計(jì)算。 一個(gè) Promise 處于以下三種狀態(tài)之一: pend...
摘要:這就是積極的函數(shù)式編程。上一章翻譯連載第章遞歸下輕量級(jí)函數(shù)式編程你不知道的姊妹篇原創(chuàng)新書(shū)移動(dòng)前端高效開(kāi)發(fā)實(shí)戰(zhàn)已在亞馬遜京東當(dāng)當(dāng)開(kāi)售。 原文地址:Functional-Light-JS 原文作者:Kyle Simpson-《You-Dont-Know-JS》作者 關(guān)于譯者:這是一個(gè)流淌著滬江血液的純粹工程:認(rèn)真,是 HTML 最堅(jiān)實(shí)的梁柱;分享,是 CSS 里最閃耀的一瞥;總...
閱讀 1273·2021-09-23 11:51
閱讀 1391·2021-09-04 16:45
閱讀 633·2019-08-30 15:54
閱讀 2087·2019-08-30 15:52
閱讀 1605·2019-08-30 11:17
閱讀 3107·2019-08-29 13:59
閱讀 2023·2019-08-28 18:09
閱讀 389·2019-08-26 12:15