摘要:討論還請到原下什么是異步迭代器關(guān)注或者通過其他渠道關(guān)注發(fā)展的同學(xué)應(yīng)該早已注意到了一個新的草案。這項草案就是我本文中,我將要提到的異步迭代器。因此我去學(xué)習(xí)異步迭代器,自然也是為了解決我在業(yè)務(wù)中所遇到的問題。
討論還請到原 github issue 下:https://github.com/LeuisKen/l...什么是異步迭代器
關(guān)注tc39或者通過其他渠道關(guān)注JavaScript發(fā)展的同學(xué)應(yīng)該早已注意到了一個新的草案:proposal-async-iteration。該草案在本文成文時,已經(jīng)進(jìn)入了ECMAScript? 2019規(guī)范,也就是說,成為了JavaScript語言本身的一部分。這項草案就是我本文中,我將要提到的異步迭代器(Asynchronous Iterators)。
這個新的語法,為之前的生成器函數(shù)(generator function)提供了異步的能力。舉個例子,就是下面這樣。
// 之前的生成器函數(shù) function* sampleGenerator(array) { for (let i = 0; i < array.length; i++) { yield array[i]; } } // 現(xiàn)在的異步生成器函數(shù),讓我們可以在生成器函數(shù)前面加上 async 關(guān)鍵字 async function* sampleAsyncGenerator(getItemByPageNumber, totalPages) { for (let i = 0; i < totalPages; i++) { // 這樣我們就能在里面使用 await 了 yield await getItemByPageNumber(i); } }業(yè)務(wù)場景
我們學(xué)習(xí)新的東西,必然是要伴隨著業(yè)務(wù)價值的。因此我去學(xué)習(xí)異步迭代器,自然也是為了解決我在業(yè)務(wù)中所遇到的問題。接下來我來分享一個場景:
在移動端,經(jīng)常會有滑到頁面底部,加載更多的場景。比如,我們在瀏覽新聞的時候,選擇一個分類,就能看到對應(yīng)分類的很多新聞,這些新聞通常是新的在前,舊的在后,順序的排列下來。例如,百度新聞:https://news.baidu.com/news#/
本質(zhì)上,這是一個分頁器。通常的實(shí)現(xiàn)是,前端向服務(wù)端發(fā)送一個帶有指定類別、指定頁碼(或者時間戳)的數(shù)據(jù)請求,服務(wù)端返回一個數(shù)據(jù)列表,該列表長度通常是固定的。然后前端在拿到這部分?jǐn)?shù)據(jù)后,將數(shù)據(jù)渲染到視圖上。值得我們注意的是,在這個場景下,因?yàn)槭怯脩艋瑒拥降撞浚|發(fā)對下一頁的加載,所以是不存在從第一頁跳到第五頁這種跳頁的需求的。
我們也許會用這樣的代碼來實(shí)現(xiàn)這個需求:
let page = 1; // 從第一頁開始 let isLastPage = false; function getPage(type) { $.ajax({ url: "/api/list", data: { page, type }, success(res) { isLastPage = res.isLastPage; // 是否為最后頁 // 根據(jù) res 更新視圖 page++; } }) } // 用戶觸發(fā)加載的事件處理函數(shù) function handleLoadEvent() { if (isLastPage) { return; } getPage("推薦"); }
不去管一些其他的實(shí)現(xiàn)細(xì)節(jié)(如,throttle、異步競態(tài)),這段代碼雖然不甚優(yōu)雅,但是足夠?qū)崿F(xiàn)我們的業(yè)務(wù)需求了。
需求總是會變的假設(shè)不久之后,我們接到了一個新的需求,我們業(yè)務(wù)中的某兩個(或者三個、四個)類別的列表需要在同一個頁面上展示。也就是說,數(shù)據(jù)的映射關(guān)系,發(fā)生了如下改變:
方案設(shè)計讓我們先思考一下:如何去合并列表數(shù)據(jù),讓我們的列表還能像之前一樣保證有序?為了方便討論,我在這里抽象出兩個數(shù)據(jù)源A、B,他們里面的內(nèi)容是兩個有序數(shù)組,如下所示:
A ---> [1, 3, 5, 7, 9, 11, …] B ---> [0, 2, 4, 6, 8, …]
那么我們預(yù)期的合并后列表就是:
merged ---> [0, 1, 2, 3, 4, 5, 6, …]
假設(shè)我們每次分頁去取數(shù)據(jù),預(yù)期的數(shù)據(jù)長度(記為:pickNumber)是3,那么我們在第一次取數(shù)據(jù)后,回調(diào)中預(yù)期請求到的值就是[0, 1, 2]。那么如果我們從A中拿3個,B中也拿3個,那么排序后,從排序的結(jié)果中取3個,就拿到了我們想要的[0, 1, 2]。要取出合并后列表中有序的pickNumber個數(shù)據(jù),就先從各個數(shù)據(jù)源中取pickNumber個數(shù)據(jù),然后對結(jié)果排序,取出前pickNumber個數(shù)據(jù),這就是我所選擇的保證數(shù)據(jù)有序的策略。
這個策略,在一些極限情況下,比如合并后列表的前幾頁都是A等等,都是可以保證順序的。
實(shí)現(xiàn)設(shè)計方案確定后,我們來設(shè)計下我們要實(shí)現(xiàn)的函數(shù),很自然的,我們會想到這樣的實(shí)現(xiàn):
/** * 從多個 type 列表中獲取數(shù)據(jù) * * @param {Array} types 需要合并的 type 列表 * @param {Function} sortFn 排序函數(shù) * @param {number} pickNumber 每頁需要的數(shù)據(jù) * @param {Function} callback 返回頁數(shù)據(jù)的回調(diào)函數(shù) */ function getListFromMultiTypes(types, sortFn, pickNumber, callback) { }
這樣的實(shí)現(xiàn),做出來其實(shí)也是可以滿足業(yè)務(wù)需求的,但是他不是我想要的。因?yàn)?b>type這個東西和業(yè)務(wù)耦合的太嚴(yán)重了。當(dāng)然,我可以把types改成urls,但是這種程度的抽象,還是需要我們把$.ajax這個東西內(nèi)置到我們的函數(shù)里,而我想要的僅僅只是一個merge。所以,我們還是需要去追求更好的形式來抽象這個業(yè)務(wù)。
追求更好的抽象下面我把前面的A和B換一種形式組織起來,如果我們忽略掉他們其實(shí)是異步的東西的話,其實(shí)他們可以被抽象為二維數(shù)組:
// A [ [1, 3, 5], [7, 9, 11], … ] // B [ [0, 2, 4], [6, 8, 10], … ]
抽象成了二維數(shù)組,我們可以發(fā)現(xiàn)只要去迭代A、B,我們就可以獲得想要的數(shù)據(jù)了。也就是說,A和B其實(shí)就是兩個不同的迭代器。加上異步的話,那么一個分頁的服務(wù)端列表數(shù)據(jù)源,在前端可以抽象成一個異步的迭代器,這樣抽象后,我的需求,就變成了把兩個數(shù)組merge一下就ok了~
使用異步生成器函數(shù)抽象分頁邏輯我們可以用Promise將$.ajax的邏輯封裝一下:
/** * 請求數(shù)據(jù),返回 Promise * * @param {string} url 請求的 url * @param {Object} data 請求所帶的 query 參數(shù) * @return {Promise} 用于處理請求的 Promise 對象 */ function getData(url, data) { return new Promise(function (resolve, reject) { $.ajax({ url, type: "GET", data, success: resolve }); }); }
這樣,一個分頁器的異步生成器函數(shù)就可以用如下代碼實(shí)現(xiàn):
/** * 獲取 github 某倉庫的 issue 列表 * * @param {string} location 倉庫路徑,如:facebook/react */ async function* getRepoIssue(location) { let page = 1; let isLastPage = false; while (!isLastPage) { let lastRes = await getData( "/api/issues", {location, page} ); isLastPage = lastRes.length < PAGE_SIZE; page++; yield lastRes; } }
使用起來可以說是非常簡單了:
const list = getRepoIssue("facebook/react"); btn.addEventListener("click", async function () { const {value, done} = await list.next(); if (done) { return; } container.innerHTML += value.reduce((cur, next) => cur + `
有了異步迭代器的抽象,我們重新來看看我們的設(shè)計,相信大家心中都有了答案:
/** * 合并多個異步迭代器,返回一個新的異步迭代器 * 該迭代器每次返回 pickNumber 個數(shù)據(jù) * 數(shù)據(jù)按照 sortFn 排序 * * @param {Array} iterators 異步迭代器數(shù)組對象 * @param {Function} sortFn 對請求結(jié)果進(jìn)行排序的函數(shù) * @param {number} pickNumber 迭代器每次返回的元素數(shù)量 * @return {Iterator} 合并后的異步迭代器 */ export default async function* mixLoader(iterators, sortFn, pickNumber) { }實(shí)現(xiàn)
mixLoader取意是混合的加載器(老實(shí)說,并不是一個非常合適的名字),這個函數(shù)我做了一版最簡單的實(shí)現(xiàn),后續(xù) @STLighter 幫我從算法層面上進(jìn)行了多次優(yōu)化,在此非常感謝~~
github倉庫地址:https://github.com/LeuisKen/m...
第一版實(shí)現(xiàn)(雖然實(shí)現(xiàn)的不好但是好在原理簡單):https://github.com/LeuisKen/m...
@STLighter 優(yōu)化后的實(shí)現(xiàn):https://github.com/LeuisKen/m...
結(jié)語還請注意,如果是有跳頁需求的話,就不能這么封裝了
除了更好的抽象帶來的可讀性,代碼也變得更加容易測試了
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/94441.html
摘要:文章系列設(shè)計模式單例模式設(shè)計模式策略模式設(shè)計模式代理模式概念迭代器模式是指提供一種方法順序訪問一個聚合對象中的各個元素,而又不需要暴露該對象的內(nèi)部表示。 前言 本系列文章主要根據(jù)《JavaScript設(shè)計模式與開發(fā)實(shí)踐》整理而來,其中會加入了一些自己的思考。希望對大家有所幫助。 文章系列 js設(shè)計模式--單例模式 js設(shè)計模式--策略模式 js設(shè)計模式--代理模式 概念 迭代器模式是指...
摘要:我們還能如何使用生成器作為迭代器的能力使對象可迭代。一些重要的事件值得了解生成器是由布倫丹艾希首次在上實(shí)現(xiàn)的。布倫丹艾希的設(shè)計是緊緊跟隨由啟發(fā)的生成器。 什么是生成器? 我們先從下面的這里例子開始。 function* quips(name) { yield hello + name + !; yield i hope you are enjoying the blog po...
摘要:在種,使用關(guān)鍵字定義的迭代器也被稱為生成器迭代器迭代器是訪問集合內(nèi)元素的一種方式。調(diào)用任何定義包含關(guān)鍵字的函數(shù)都不會執(zhí)行該函數(shù),而是會獲得一個隊?wèi)?yīng)于該函數(shù)的迭代器。 上一篇文章:Python:Tornado 第一章:異步及協(xié)程基礎(chǔ):第一節(jié):同步與異步I/O下一篇文章:Python:Tornado 第一章:異步及協(xié)程基礎(chǔ):第三節(jié):協(xié)程 協(xié)程是Tornado中進(jìn)行異步I/O代碼開發(fā)的方法...
閱讀 2553·2021-11-23 09:51
閱讀 3355·2021-11-22 15:22
閱讀 1868·2021-11-18 13:22
閱讀 2236·2021-09-24 09:48
閱讀 1308·2019-08-29 13:58
閱讀 1297·2019-08-26 13:39
閱讀 2445·2019-08-26 10:48
閱讀 3031·2019-08-26 10:21