国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專(zhuān)欄INFORMATION COLUMN

異步迭代器在業(yè)務(wù)中的實(shí)踐

Flands / 1295人閱讀

摘要:討論還請(qǐng)到原下什么是異步迭代器關(guān)注或者通過(guò)其他渠道關(guān)注發(fā)展的同學(xué)應(yīng)該早已注意到了一個(gè)新的草案。這項(xiàng)草案就是我本文中,我將要提到的異步迭代器。因此我去學(xué)習(xí)異步迭代器,自然也是為了解決我在業(yè)務(wù)中所遇到的問(wèn)題。

討論還請(qǐng)到原 github issue 下:https://github.com/LeuisKen/l...
什么是異步迭代器

關(guān)注tc39或者通過(guò)其他渠道關(guān)注JavaScript發(fā)展的同學(xué)應(yīng)該早已注意到了一個(gè)新的草案:proposal-async-iteration。該草案在本文成文時(shí),已經(jīng)進(jìn)入了ECMAScript? 2019規(guī)范,也就是說(shuō),成為了JavaScript語(yǔ)言本身的一部分。這項(xiàng)草案就是我本文中,我將要提到的異步迭代器(Asynchronous Iterators)

這個(gè)新的語(yǔ)法,為之前的生成器函數(shù)(generator function)提供了異步的能力。舉個(gè)例子,就是下面這樣。

// 之前的生成器函數(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ù)場(chǎng)景

我們學(xué)習(xí)新的東西,必然是要伴隨著業(yè)務(wù)價(jià)值的。因此我去學(xué)習(xí)異步迭代器,自然也是為了解決我在業(yè)務(wù)中所遇到的問(wèn)題。接下來(lái)我來(lái)分享一個(gè)場(chǎng)景:

在移動(dòng)端,經(jīng)常會(huì)有滑到頁(yè)面底部,加載更多的場(chǎng)景。比如,我們?cè)跒g覽新聞的時(shí)候,選擇一個(gè)分類(lèi),就能看到對(duì)應(yīng)分類(lèi)的很多新聞,這些新聞通常是新的在前,舊的在后,順序的排列下來(lái)。例如,百度新聞:https://news.baidu.com/news#/

本質(zhì)上,這是一個(gè)分頁(yè)器。通常的實(shí)現(xiàn)是,前端向服務(wù)端發(fā)送一個(gè)帶有指定類(lèi)別、指定頁(yè)碼(或者時(shí)間戳)的數(shù)據(jù)請(qǐng)求,服務(wù)端返回一個(gè)數(shù)據(jù)列表,該列表長(zhǎng)度通常是固定的。然后前端在拿到這部分?jǐn)?shù)據(jù)后,將數(shù)據(jù)渲染到視圖上。值得我們注意的是,在這個(gè)場(chǎng)景下,因?yàn)槭怯脩?hù)滑動(dòng)到底部,觸發(fā)對(duì)下一頁(yè)的加載,所以是不存在從第一頁(yè)跳到第五頁(yè)這種跳頁(yè)的需求的。

我們也許會(huì)用這樣的代碼來(lái)實(shí)現(xiàn)這個(gè)需求:

let page = 1;       // 從第一頁(yè)開(kāi)始
let isLastPage = false;

function getPage(type) {
    $.ajax({
        url: "/api/list",
        data: {
            page,
            type
        },
        success(res) {
            isLastPage = res.isLastPage;    // 是否為最后頁(yè)
            // 根據(jù) res 更新視圖
            page++;
        }
    })
}

// 用戶(hù)觸發(fā)加載的事件處理函數(shù)
function handleLoadEvent() {
    if (isLastPage) {
        return;
    }
    getPage("推薦");
}

不去管一些其他的實(shí)現(xiàn)細(xì)節(jié)(如,throttle、異步競(jìng)態(tài)),這段代碼雖然不甚優(yōu)雅,但是足夠?qū)崿F(xiàn)我們的業(yè)務(wù)需求了。

需求總是會(huì)變的

假設(shè)不久之后,我們接到了一個(gè)新的需求,我們業(yè)務(wù)中的某兩個(gè)(或者三個(gè)、四個(gè))類(lèi)別的列表需要在同一個(gè)頁(yè)面上展示。也就是說(shuō),數(shù)據(jù)的映射關(guān)系,發(fā)生了如下改變:

方案設(shè)計(jì)

讓我們先思考一下:如何去合并列表數(shù)據(jù),讓我們的列表還能像之前一樣保證有序?為了方便討論,我在這里抽象出兩個(gè)數(shù)據(jù)源A、B,他們里面的內(nèi)容是兩個(gè)有序數(shù)組,如下所示:

A ---> [1, 3, 5, 7, 9, 11, …]
B ---> [0, 2, 4, 6, 8, …]

那么我們預(yù)期的合并后列表就是:

merged ---> [0, 1, 2, 3, 4, 5, 6, …]

假設(shè)我們每次分頁(yè)去取數(shù)據(jù),預(yù)期的數(shù)據(jù)長(zhǎng)度(記為:pickNumber)是3,那么我們?cè)诘谝淮稳?shù)據(jù)后,回調(diào)中預(yù)期請(qǐng)求到的值就是[0, 1, 2]。那么如果我們從A中拿3個(gè),B中也拿3個(gè),那么排序后,從排序的結(jié)果中取3個(gè),就拿到了我們想要的[0, 1, 2]。要取出合并后列表中有序的pickNumber個(gè)數(shù)據(jù),就先從各個(gè)數(shù)據(jù)源中取pickNumber個(gè)數(shù)據(jù),然后對(duì)結(jié)果排序,取出前pickNumber個(gè)數(shù)據(jù),這就是我所選擇的保證數(shù)據(jù)有序的策略。

這個(gè)策略,在一些極限情況下,比如合并后列表的前幾頁(yè)都是A等等,都是可以保證順序的。

實(shí)現(xiàn)設(shè)計(jì)

方案確定后,我們來(lái)設(shè)計(jì)下我們要實(shí)現(xiàn)的函數(shù),很自然的,我們會(huì)想到這樣的實(shí)現(xiàn):

/**
 * 從多個(gè) type 列表中獲取數(shù)據(jù)
 *
 * @param {Array} types 需要合并的 type 列表
 * @param {Function} sortFn 排序函數(shù)
 * @param {number} pickNumber 每頁(yè)需要的數(shù)據(jù)
 * @param {Function} callback 返回頁(yè)數(shù)據(jù)的回調(diào)函數(shù)
 */
function getListFromMultiTypes(types, sortFn, pickNumber, callback) {

}

這樣的實(shí)現(xiàn),做出來(lái)其實(shí)也是可以滿(mǎn)足業(yè)務(wù)需求的,但是他不是我想要的。因?yàn)?b>type這個(gè)東西和業(yè)務(wù)耦合的太嚴(yán)重了。當(dāng)然,我可以把types改成urls,但是這種程度的抽象,還是需要我們把$.ajax這個(gè)東西內(nèi)置到我們的函數(shù)里,而我想要的僅僅只是一個(gè)merge。所以,我們還是需要去追求更好的形式來(lái)抽象這個(gè)業(yè)務(wù)。

追求更好的抽象

下面我把前面的A和B換一種形式組織起來(lái),如果我們忽略掉他們其實(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ù)了。也就是說(shuō),A和B其實(shí)就是兩個(gè)不同的迭代器。加上異步的話,那么一個(gè)分頁(yè)的服務(wù)端列表數(shù)據(jù)源,在前端可以抽象成一個(gè)異步的迭代器,這樣抽象后,我的需求,就變成了把兩個(gè)數(shù)組merge一下就ok了~

使用異步生成器函數(shù)抽象分頁(yè)邏輯

我們可以用Promise$.ajax的邏輯封裝一下:

/**
 * 請(qǐng)求數(shù)據(jù),返回 Promise
 *
 * @param {string} url 請(qǐng)求的 url
 * @param {Object} data 請(qǐng)求所帶的 query 參數(shù)
 * @return {Promise} 用于處理請(qǐng)求的 Promise 對(duì)象
 */
function getData(url, data) {
    return new Promise(function (resolve, reject) {
        $.ajax({
            url,
            type: "GET",
            data,
            success: resolve
        });
    });
}

這樣,一個(gè)分頁(yè)器的異步生成器函數(shù)就可以用如下代碼實(shí)現(xiàn):

/**
 * 獲取 github 某倉(cāng)庫(kù)的 issue 列表
 *
 * @param {string} location 倉(cāng)庫(kù)路徑,如: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;
    }
}

使用起來(lái)可以說(shuō)是非常簡(jiǎn)單了:

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 + `
  • Repo: ${next.repository_url}
    ` + `
    Title: ${next.title}
    ` + `
    Time: ${next.created_at}
    `, ""); });
  • 再設(shè)計(jì)

    有了異步迭代器的抽象,我們重新來(lái)看看我們的設(shè)計(jì),相信大家心中都有了答案:

    /**
     * 合并多個(gè)異步迭代器,返回一個(gè)新的異步迭代器
     * 該迭代器每次返回 pickNumber 個(gè)數(shù)據(jù)
     * 數(shù)據(jù)按照 sortFn 排序
     *
     * @param {Array} iterators 異步迭代器數(shù)組對(duì)象
     * @param {Function} sortFn 對(duì)請(qǐng)求結(jié)果進(jìn)行排序的函數(shù)
     * @param {number} pickNumber 迭代器每次返回的元素?cái)?shù)量
     * @return {Iterator} 合并后的異步迭代器
     */
    export default async function* mixLoader(iterators, sortFn, pickNumber) {
    
    }
    實(shí)現(xiàn)

    mixLoader取意是混合的加載器(老實(shí)說(shuō),并不是一個(gè)非常合適的名字),這個(gè)函數(shù)我做了一版最簡(jiǎn)單的實(shí)現(xiàn),后續(xù) @STLighter 幫我從算法層面上進(jìn)行了多次優(yōu)化,在此非常感謝~~

    github倉(cāng)庫(kù)地址:https://github.com/LeuisKen/m...

    第一版實(shí)現(xiàn)(雖然實(shí)現(xiàn)的不好但是好在原理簡(jiǎn)單):https://github.com/LeuisKen/m...

    @STLighter 優(yōu)化后的實(shí)現(xiàn):https://github.com/LeuisKen/m...

    結(jié)語(yǔ)

    還請(qǐng)注意,如果是有跳頁(yè)需求的話,就不能這么封裝了

    除了更好的抽象帶來(lái)的可讀性,代碼也變得更加容易測(cè)試了

    文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

    轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/94441.html

    相關(guān)文章

    • js設(shè)計(jì)模式--迭代器模式

      摘要:文章系列設(shè)計(jì)模式單例模式設(shè)計(jì)模式策略模式設(shè)計(jì)模式代理模式概念迭代器模式是指提供一種方法順序訪問(wèn)一個(gè)聚合對(duì)象中的各個(gè)元素,而又不需要暴露該對(duì)象的內(nèi)部表示。 前言 本系列文章主要根據(jù)《JavaScript設(shè)計(jì)模式與開(kāi)發(fā)實(shí)踐》整理而來(lái),其中會(huì)加入了一些自己的思考。希望對(duì)大家有所幫助。 文章系列 js設(shè)計(jì)模式--單例模式 js設(shè)計(jì)模式--策略模式 js設(shè)計(jì)模式--代理模式 概念 迭代器模式是指...

      binta 評(píng)論0 收藏0
    • ES6——生成器

      摘要:我們還能如何使用生成器作為迭代器的能力使對(duì)象可迭代。一些重要的事件值得了解生成器是由布倫丹艾希首次在上實(shí)現(xiàn)的。布倫丹艾希的設(shè)計(jì)是緊緊跟隨由啟發(fā)的生成器。 什么是生成器? 我們先從下面的這里例子開(kāi)始。 function* quips(name) { yield hello + name + !; yield i hope you are enjoying the blog po...

      cgh1999520 評(píng)論0 收藏0
    • Python:Tornado 第一章:異步及協(xié)程基礎(chǔ):第二節(jié):Python關(guān)鍵字yield

      摘要:在種,使用關(guān)鍵字定義的迭代器也被稱(chēng)為生成器迭代器迭代器是訪問(wèn)集合內(nèi)元素的一種方式。調(diào)用任何定義包含關(guān)鍵字的函數(shù)都不會(huì)執(zhí)行該函數(shù),而是會(huì)獲得一個(gè)隊(duì)?wèi)?yīng)于該函數(shù)的迭代器。 上一篇文章:Python:Tornado 第一章:異步及協(xié)程基礎(chǔ):第一節(jié):同步與異步I/O下一篇文章:Python:Tornado 第一章:異步及協(xié)程基礎(chǔ):第三節(jié):協(xié)程 協(xié)程是Tornado中進(jìn)行異步I/O代碼開(kāi)發(fā)的方法...

      reclay 評(píng)論0 收藏0

    發(fā)表評(píng)論

    0條評(píng)論

    最新活動(dòng)
    閱讀需要支付1元查看
    <