成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

異步發(fā)展流程 —— Generators + co 讓異步更優(yōu)雅

dingda / 2949人閱讀

摘要:遍歷器原有的表示集合的數(shù)據(jù)結(jié)構(gòu),主要有和,在中又加入了和,這樣就有了四種數(shù)據(jù)集合,還可以組合使用它們,如數(shù)組的成員是或,這樣就需要一種統(tǒng)一的接口機(jī)制,用來(lái)處理所有不同的數(shù)據(jù)結(jié)構(gòu)。

閱讀原文


Generators 簡(jiǎn)介

Generator 函數(shù)是 ES6 提供的一種異步編程解決方案,是一個(gè)生成器,用于生成一個(gè)遍歷器的函數(shù),語(yǔ)法行為與傳統(tǒng)函數(shù)完全不同。

Iterator 遍歷器

JavaScript 原有的表示 “集合” 的數(shù)據(jù)結(jié)構(gòu),主要有 ArrayObject,在 ES6 中又加入了 SetMap,這樣就有了四種數(shù)據(jù)集合,還可以組合使用它們,如數(shù)組的成員是 MapObject,這樣就需要一種統(tǒng)一的接口機(jī)制,用來(lái)處理所有不同的數(shù)據(jù)結(jié)構(gòu)。

遍歷器 Iterator 就是這樣一種機(jī)制,它是一種接口,為不同的數(shù)據(jù)結(jié)構(gòu)提供統(tǒng)一的、簡(jiǎn)便的訪問(wèn)機(jī)制,任何數(shù)據(jù)結(jié)構(gòu)只要部署了 Iterator 接口,就可以完成遍歷操作,即依次處理該數(shù)據(jù)結(jié)構(gòu)的所有成員。

Iterator 遍歷器其實(shí)就是一個(gè)指針對(duì)象,上面有 next 方法,第一次調(diào)用 next 指針指向數(shù)據(jù)結(jié)構(gòu)的第一個(gè)成員,第二次 next 調(diào)用指針指向第二個(gè)成員,直到指針指向最后一個(gè)成員。

我們可以使用 ES6 的展開運(yùn)算符 ...for...of... 去遍歷帶有 Iterator 接口的數(shù)據(jù)結(jié)構(gòu),需要注意的是,Object 本身不具備 Iterator 接口,所以我們無(wú)法通過(guò) ... 把一個(gè)對(duì)象擴(kuò)展到一個(gè)數(shù)組中,并且會(huì)報(bào)錯(cuò),我們可以通過(guò)代碼手動(dòng)將 Object 類型實(shí)現(xiàn) Iterator 接口。

// 給對(duì)象擴(kuò)展 Iterator 接口
// 通過(guò) Generator 函數(shù)給 Object 擴(kuò)展 Iterator 接口
Object.prototype[Symbol.iterator] = function*() {
    for (var key in this) {
        yield this[key];
    }
};

// 測(cè)試 Iterator 接口
let obj = {
    a: 1,
    b: 2,
    c: 3
};

let arr = [...obj];

console.log(arr); // [1, 2, 3]

上面我們其實(shí)是通過(guò) ES6 的 Generator 函數(shù)簡(jiǎn)單粗暴的給 Object 類型實(shí)現(xiàn)了 Iterator 接口,后面我們會(huì)簡(jiǎn)單模擬 Generator 生成器。

模擬 Generator

Generator 函數(shù)是一個(gè)生成器,調(diào)用后會(huì)返回給我們一個(gè) Iterator 遍歷器對(duì)象,在對(duì)象中有一個(gè) next 方法,調(diào)用一次 next,幫我遍歷一次,返回值為一個(gè)對(duì)象,內(nèi)部有 valuedone 兩個(gè)屬性,value 屬性代表當(dāng)前遍歷的值,done 屬性代表是否遍歷完成,如果遍歷完成后繼續(xù)調(diào)用 next,返回的對(duì)象中 value 屬性值為 undefined,done 屬性值為 true,這個(gè)遍歷器在進(jìn)行數(shù)據(jù)遍歷時(shí)更像給我們提供了一個(gè)暫停功能,每次都需要手動(dòng)調(diào)用 next 去進(jìn)行下一次遍歷。

我們根據(jù) Generator 的特性用 ES5 簡(jiǎn)單模擬一個(gè)遍歷器生成函數(shù):

// 模擬遍歷器生成函數(shù)
function iterator(arr) {
    var i = 0;

    return {
        next: function() {
            var done = i >= arr.length;
            var value = !done ? arr[i++] : undefined;

            return {
                value: value,
                done: done
            };
        }
    };
}

測(cè)試一下模擬的遍歷器生成函數(shù):

// 測(cè)試 iterator 函數(shù)
var arr = [1, 3, 5];

// 遍歷器
var result = iterator(arr);

result.next(); // {value: 1, done: false}
result.next(); // {value: 3, done: false}
result.next(); // {value: 5, done: false}
result.next(); // {value: undefined, done: true}
Generator 的基本使用

在普通的函數(shù) function 關(guān)鍵字后加一個(gè) * 就代表聲明了一個(gè)生成器函數(shù),執(zhí)行后返回一個(gè)遍歷器對(duì)象,每次調(diào)用遍歷器的 next 方法時(shí),遇到 yield 關(guān)鍵字暫停執(zhí)行,并將 yield 關(guān)鍵字后面的值會(huì)作為返回對(duì)象中 value 的值,如果函數(shù)有返回值,會(huì)把返回值作為調(diào)用 next 方法進(jìn)行遍歷完成后返回的對(duì)象中 value 的值,果已經(jīng)遍歷完成,再次 next 調(diào)用這個(gè) value 的值會(huì)變成 undefined。

// 生成器函數(shù)
function* gen() {
    yield 1;
    yield 2;
    return 3;
}

// 遍歷器
let it = gen();

it.next(); // {value: 1, done: false}
it.next(); // {value: 2, done: false}
it.next(); // {value: 3, done: true}
it.next(); // {value: undefined, done: true}

在 Generator 函數(shù)中可以使用變量接收 yield 關(guān)鍵字執(zhí)行后的返回值,只是接收的值并不是 yield 關(guān)鍵字后面表達(dá)式執(zhí)行的結(jié)果,而是遍歷器在下一次調(diào)用 next 方法時(shí)傳入的參數(shù)。

也就是說(shuō)我們第一次調(diào)用 next 方法進(jìn)行遍歷時(shí)是不需要傳遞參數(shù)的,因?yàn)樯厦娌](méi)有變量來(lái)接收它,即使傳參也會(huì)被忽略掉,我們用一個(gè)例子感受一下這種比較特殊的執(zhí)行機(jī)制:

// 生成器函數(shù)
function* gen(arr) {
    let a = yield 1;
    let b = yield a;
    let c = yield b;
    return c;
}

// 遍歷器
let it = gen();

it.next(); // {value: 1, done: false}
it.next(2); // {value: 2, done: false}
it.next(3); // {value: 3, done: false}
it.next(4); // {value: 4, done: true}
it.next(5); // {value: undefined, done: true}

如果已經(jīng)遍歷完成,并把上次遍歷接收到的值作為返回值傳遞給返回對(duì)象 value 屬性的值,后面再次調(diào)用 next 傳入的參數(shù)也會(huì)被忽略,返回對(duì)象的 value 值為 undefined。

在 Generator 函數(shù)中,如果在其他函數(shù)或方法調(diào)用的回調(diào)內(nèi)部(函數(shù)的執(zhí)行上/下文發(fā)生變化)不能直接使用 yield 關(guān)鍵字。

// 循環(huán)中使用 yield
// 錯(cuò)誤的寫法
function* gen(arr) {
    arr.forEach(*item => {
        yield* item;
    });
}

// 正確的寫法
function* gen(arr) {
    for(let i = 0; i < arr.length; i++) {
        yield arr[i];
    }
}

如果在一個(gè) Generator 函數(shù)中調(diào)用了另一個(gè) Generator 函數(shù),在調(diào)用外層函數(shù)返回遍歷器的 next 方法時(shí)是不會(huì)遍歷內(nèi)部函數(shù)返回的遍歷器的。

// 合并生成器 —— 錯(cuò)誤
// 外層的生成器函數(shù)
function* genOut() {
    yield "a";
    yield genIn();
    yield "c";
}

// 內(nèi)層的生成器函數(shù)
function* genIn() {
    yield "b";
}

// 遍歷器
let it = genOut();

it.next(); // {value: "a", done: false}
it.next(); // 返回 genIn 的遍歷器對(duì)象
it.next(); // {value: "c", done: false}
it.next(); // {value: undefined, done: true}

上面代碼如果想在調(diào)用 genOut 返遍歷器的 next 方法時(shí),同時(shí)遍歷 genIn 調(diào)用后返回的遍歷器,需要使用 yield* 表達(dá)式。

// 合并生成器 —— yield*
// 外層的生成器函數(shù)
function* genOut() {
    yield "a";
    yield* genIn();
    yield "c";
}

// 內(nèi)層的生成器函數(shù)
function* genIn() {
    yield "b";
}

// 遍歷器
let it = genOut();

it.next(); // {value: "a", done: false}
it.next(); // {value: "b", done: false}
it.next(); // {value: "c", done: false}
it.next(); // {value: undefined, done: true}

genOut 返回的遍歷器調(diào)用 next 遇到 yield* 表達(dá)式時(shí)幫我們?nèi)ケ闅v了 genIn 返回的遍歷器,其實(shí) yield* 內(nèi)部做了處理,等同于下面代碼:

// 合并生成器 —— for of
// 外層的生成器
function* genOut() {
    yield "a";
    for (let v of genIn()) {
        yield v;
    }
    yield "c";
}

// 內(nèi)層的生成器
function* genIn() {
    yield "b";
}

// 遍歷器
let it = genOut();

it.next(); // {value: "a", done: false}
it.next(); // {value: "b", done: false}
it.next(); // {value: "c", done: false}
it.next(); // {value: undefined, done: true}


Generators 與 Promise 結(jié)合

Promise 也是 ES6 的規(guī)范,同樣是解決異步的一種手段,如果對(duì) Promise 還不了解,可以閱讀下面兩篇文章:

異步發(fā)展流程 —— Promise 的基本使用

異步發(fā)展流程 —— 手寫一個(gè)符合 Promise/A+ 規(guī)范的 Promise

因?yàn)?Generator 函數(shù)在執(zhí)行時(shí)遇到 yield 關(guān)鍵字會(huì)暫停執(zhí)行,那么 yield 后面可以是異步操作的代碼,比如 Promise,需要繼續(xù)執(zhí)行,就手動(dòng)調(diào)用返回遍歷器的 next 方法,因?yàn)橹虚g有一個(gè)等待的過(guò)程,所以在執(zhí)行異步代碼的時(shí)候避免了回調(diào)函數(shù)的嵌套,在寫法上更像同步,更容易理解。

我們來(lái)設(shè)計(jì)一個(gè) Generator 函數(shù)與 Promise 異步操作結(jié)合的使用場(chǎng)景,假設(shè)我們需要使用 NodeJS 的 fs 模塊讀取一個(gè)文件 a.txt 的內(nèi)容,而 a.txt 的內(nèi)容是另一個(gè)需要讀取文件 b.txt 的文件名,讀取 b.txt 最后打印讀取到的內(nèi)容 “Hello world”。

回調(diào)函數(shù)的實(shí)現(xiàn):

// 連續(xù)讀取文件 —— 異步回調(diào)
// 引入依賴
const fs = require("fs");

fs.readFile("a.txt", "utf8", (err, data) => {
    if (!err) {
        fs.readFile(data, "utf8", (err, data) => {
            if (!err) {
                console.log(data); // Hello world
            }
        });
    }
});

上面代碼因?yàn)橹挥袃蓪踊卣{(diào)函數(shù)嵌套,所以感覺(jué)沒(méi)那么復(fù)雜,但是嵌套的回調(diào)函數(shù)多了,代碼就不那么的優(yōu)雅了,我們接下來(lái)使用 Generator 結(jié)合 Promise 來(lái)實(shí)現(xiàn),為了方便將 fs 異步的方法轉(zhuǎn)換成 Promise,我們引入 util 模塊,并轉(zhuǎn)換 readFile 方法。

// 連續(xù)讀取文件 —— Generator + Promise
// 引入依賴
const fs = require("fs");
const util = require("util");

// 將 readFile 方法轉(zhuǎn)換成 Promise
const read = util.promisify(fs.readFile);

// 生成器函數(shù)
function* gen() {
    let aData = yield read("1.txt", "utf8");
    let bData = yield read(aData, "utf8");
    return bData;
}

// 遍歷器
let it = gen();

it.next().value.then(data => {
    it.next(data).then(data => {
        console.log(data); // Hello world
    });
});

我們只看 Generator 函數(shù) gen 內(nèi)部的執(zhí)行,雖然是異步操作,但是在寫法上幾乎和同步?jīng)]有區(qū)別,理解起來(lái)更容易,唯一美中不足的是,我們需要自己手動(dòng)的調(diào)用遍歷器的 next 和 Promise 實(shí)例的 then,這個(gè)問(wèn)題 co 庫(kù)可以幫我們解決。

co 庫(kù)的使用

co 庫(kù)的作者是著名的 NodeJS 大神 TJ,是基于 Generator 和 Promise 的,這個(gè)庫(kù)能幫我們實(shí)現(xiàn)自動(dòng)調(diào)用 Generator 函數(shù)返回遍歷器的 next 方法,并執(zhí)行 yield 后面 Promise 實(shí)例的 then 方法,所以每次 yield 后面的異步操作返回的必須是一個(gè) Promise 實(shí)例,代碼看起來(lái)像同步,執(zhí)行其實(shí)是異步,不用自己手動(dòng)進(jìn)行下一次遍歷,這更是我們想要的。

由于 co 是一個(gè)第三方的模塊,所以在使用時(shí)需要我們提前下載:

npm install co

我們使用 co 來(lái)實(shí)現(xiàn)之前異步連續(xù)讀文件的案例:

// 連續(xù)讀取文件 —— Generator + co
// 引入依賴
const fs = require("fs");
const util = require("util");
const co = require("co");

// 將 readFile 方法轉(zhuǎn)換成 Promise
const read = util.promisify(fs.readFile);

// 生成器函數(shù)
function* gen() {
    let aData = yield read("1.txt", "utf8");
    let bData = yield read(aData, "utf8");
    return bData;
}

// 使用 co 庫(kù)代替手動(dòng)調(diào)用 next
co(gen()).then(data => {
    console.log(data); // Hello world
});

從上面代碼可以看出,co 庫(kù)的 co 函數(shù)參數(shù)是一個(gè)遍歷器,即 Generator 函數(shù)執(zhí)行后的返回結(jié)果,在 co 內(nèi)部操作遍歷器并遍歷完成后返回了一個(gè) Promise 實(shí)例,遍歷器最終的返回結(jié)果的 value 值作為 then 方法回調(diào)的參數(shù),所以我們可以使用 then 對(duì)結(jié)果進(jìn)行后續(xù)的處理。

co 庫(kù)的實(shí)現(xiàn)原理

我們其實(shí)在上面使用 co 的過(guò)程中對(duì)于 co 函數(shù)的內(nèi)部做了什么已經(jīng)有所了解,主要就是幫助我們調(diào)用遍歷器的 next 和調(diào)用 yield 后面代碼執(zhí)行后返回 Promise 實(shí)例的 then,并在整個(gè)遍歷結(jié)束后,返回一個(gè)新的 Promise 實(shí)例。

下面我們根據(jù)上面分析的 co 函數(shù)的原理來(lái)模擬一個(gè)簡(jiǎn)易版的 co 庫(kù):

// 文件:myCo.js —— co 原理
// co 函數(shù),it 為遍歷器對(duì)象
function co(it) {
    // 返回 Promise 實(shí)例
    return new Promise((resolve, reject) => {
        // 異步遞歸
        function next(data) {
            // 第一次調(diào)用 next 不需要傳參
            let { value, done } = it.next(data);

            if (!done) {
                // 如果沒(méi)完成遍歷,調(diào)用返回 Promise 的 then 方法
                value.then(data => {
                    // 如果 Promise 成功,繼續(xù)遞歸,如果失敗直接執(zhí)行 reject
                    next(data);
                }, reject);
            } else {
                // 如果遍歷完成直接執(zhí)行 resolve 并傳入 value
                resolve(value);
            }
        }
        next();
    });
}

// 導(dǎo)出模塊
module.exports = co;

驗(yàn)證 myCo.js 實(shí)現(xiàn)的 co 函數(shù):

// 驗(yàn)證 myCo
// 引入依賴
const fs = require("fs");
const util = require("util");
const myCo = require("./myCo");

// 將 readFile 方法轉(zhuǎn)換成 Promise
const read = util.promisify(fs.readFile);

// 生成器函數(shù)
function* gen() {
    let aData = yield read("1.txt", "utf8");
    let bData = yield read(aData, "utf8");
    return bData;
}

// 使用 co 庫(kù)代替手動(dòng)調(diào)用 next
myCo(gen()).then(data => {
    console.log(data); // Hello world
});

我們將引入的 co 庫(kù)替換成了自己實(shí)現(xiàn)的簡(jiǎn)易版 myCo 模塊,上面讀取文件的案例依然生效,這說(shuō)明我們模擬的 co 庫(kù)核心邏輯是沒(méi)問(wèn)題的,跟原版不同的是并沒(méi)有處理很多細(xì)節(jié),并定義指針,如果對(duì) co 庫(kù)感興趣建議看看 TJ 大神的源碼,整個(gè)庫(kù)寫的非常精簡(jiǎn),值得學(xué)習(xí)。

總結(jié)

Generators 相當(dāng)于把一個(gè)函數(shù)拆分成若干個(gè)部分執(zhí)行,執(zhí)行一次時(shí)將指針指向下一段要執(zhí)行的代碼,直到結(jié)束位置,Generators 配合 co 庫(kù)的使用場(chǎng)景多在 NodeJS 當(dāng)中,并在 Koa 1.x 版本中居多,現(xiàn)在已經(jīng)升級(jí)到 Koa 2.x 版本,使用更多的是基于 Generators 和 co 庫(kù)衍生出來(lái)的 ES7 新標(biāo)準(zhǔn) async/await,我們?cè)谙乱黄惒桨l(fā)展流程系列的文章中來(lái)詳細(xì)介紹。

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

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/98280.html

相關(guān)文章

  • 異步發(fā)展流程 —— 異步編程的終極大招 async/await

    摘要:簡(jiǎn)介指的是兩個(gè)關(guān)鍵字,是引入的新標(biāo)準(zhǔn),關(guān)鍵字用于聲明函數(shù),關(guān)鍵字用來(lái)等待異步必須是操作,說(shuō)白了就是的語(yǔ)法糖。最后希望大家在讀過(guò)異步發(fā)展流程這個(gè)系列之后,對(duì)異步已經(jīng)有了較深的認(rèn)識(shí),并可以在不同情況下游刃有余的使用這些處理異步的編程手段。 showImg(https://segmentfault.com/img/remote/1460000018998406?w=1024&h=379); ...

    zhangfaliang 評(píng)論0 收藏0
  • 《Node.js設(shè)計(jì)模式》基于ES2015+的回調(diào)控制流

    摘要:以下展示它是如何工作的函數(shù)使用構(gòu)造函數(shù)創(chuàng)建一個(gè)新的對(duì)象,并立即將其返回給調(diào)用者。在傳遞給構(gòu)造函數(shù)的函數(shù)中,我們確保傳遞給,這是一個(gè)特殊的回調(diào)函數(shù)。 本系列文章為《Node.js Design Patterns Second Edition》的原文翻譯和讀書筆記,在GitHub連載更新,同步翻譯版鏈接。 歡迎關(guān)注我的專欄,之后的博文將在專欄同步: Encounter的掘金專欄 知乎專欄...

    LiuRhoRamen 評(píng)論0 收藏0
  • 簡(jiǎn)單理解Javascript的各種異步流程控制方法

    摘要:所以僅用于簡(jiǎn)化理解,快速入門,依然需要閱讀有深入研究的文章來(lái)加深對(duì)各種異步流程控制的方法的掌握。 原文地址:http://zodiacg.net/2015/08/javascript-async-control-flow/ 隨著ES6標(biāo)準(zhǔn)逐漸成熟,利用Promise和Generator解決回調(diào)地獄問(wèn)題的話題一直很熱門。但是對(duì)解決流程控制/回調(diào)地獄問(wèn)題的各種工具認(rèn)識(shí)仍然比較麻煩。最近兩天...

    makeFoxPlay 評(píng)論0 收藏0
  • 如何理解 koa 中間件執(zhí)行機(jī)制

    摘要:注是先前版本處理異步函數(shù)的方式,通過(guò)可以將異步函數(shù)封裝成,傳入普通參數(shù)后形成僅需要參數(shù)的偏函數(shù),以此簡(jiǎn)化調(diào)用代碼目前中的偏函數(shù)已經(jīng)被無(wú)情地化了。 前幾天研究了TJ的koa/co4.x和一系列koa依賴的源碼,在知乎上做出了人生首次回答(而且我真得再也不想去知乎回答技術(shù)問(wèn)題了_(:з」∠)_),因此把文字搬到這里。 ES2015 Generator/Yield 關(guān)于Generator...

    charles_paul 評(píng)論0 收藏0
  • ES6中的異步編程:Generators函數(shù)+Promise:最強(qiáng)大的異步處理方式

    摘要:更好的異步編程上面的方法可以適用于那些比較簡(jiǎn)單的異步工作流程。小結(jié)的組合目前是最強(qiáng)大,也是最優(yōu)雅的異步流程管理編程方式。 訪問(wèn)原文地址 generators主要作用就是提供了一種,單線程的,很像同步方法的編程風(fēng)格,方便你把異步實(shí)現(xiàn)的那些細(xì)節(jié)藏在別處。這讓我們可以用一種很自然的方式書寫我們代碼中的流程和狀態(tài)邏輯,不再需要去遵循那些奇怪的異步編程風(fēng)格。 換句話說(shuō),通過(guò)將我們generato...

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

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

0條評(píng)論

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