99re热视频这里只精品,久久久天堂国产精品女人,国产av一区二区三区,久久久精品成人免费看片,99久久精品免费看国产一区二区三区

使用ES6的Generator代替回調(diào)函數(shù)

2018-06-09 16:16 更新

英文原文: Replacing callbacks with ES6 Generators

目前,已經(jīng)有很多文章討論過(guò)了如何使用ES6 generators來(lái)取代JavaScript中經(jīng)常遇到的回調(diào)金字塔。但是,其中提到的絕大多數(shù)方法都需要依賴于某個(gè)庫(kù),而對(duì)于其中的原理卻提及甚少。

在本文中,我們將一步一步的將一個(gè)基于回調(diào)函數(shù)的例子修改為一個(gè)基于generator的例子。本文的目標(biāo)是讓你透徹地理解使用generator替代回調(diào)函數(shù)的原理。

Generator是JavaScript中一個(gè)新概念,但在編程語(yǔ)言中已經(jīng)存在已久。你可能已經(jīng)在其他的編程語(yǔ)言例如Python使用過(guò)它。如果沒(méi)有,也不要害怕,我們?cè)诤竺嬉呀?jīng)為你準(zhǔn)備了一個(gè)簡(jiǎn)單明了的入門(mén)介紹。

如何運(yùn)行例子

在我們開(kāi)始之前,你需要安裝Node 0.11.*來(lái)運(yùn)行文章中的例子。當(dāng)你在運(yùn)行這些例子時(shí),你需要告訴Node使用ES6(也就是Harmony)來(lái)運(yùn)行:node -harmony example.js。

什么是generator?

在我們深入講述如何使用generator替代回調(diào)函數(shù)之前,我們先來(lái)說(shuō)說(shuō)什么是generator。

Generator很像是一個(gè)函數(shù),但是你可以暫停它的執(zhí)行。你可以向它請(qǐng)求一個(gè)值,于是它為你提供了一個(gè)值,但是余下的函數(shù)不會(huì)自動(dòng)向下執(zhí)行直到你再次向它請(qǐng)求一個(gè)值。

取號(hào)機(jī)也許是對(duì)generator的一個(gè)絕佳的比喻。你可以通過(guò)取一張票來(lái)向機(jī)器請(qǐng)求一個(gè)號(hào)碼。你接收了你的號(hào)碼,但是機(jī)器不會(huì)自動(dòng)為你提供下一個(gè)。換句話說(shuō),取票機(jī)暫停直到有人請(qǐng)求另一個(gè)號(hào)碼,此時(shí)它才會(huì)向后運(yùn)行。

ES6中的generator

Generator在ES6中像一個(gè)函數(shù)一樣被聲明,除了在之前有一個(gè)*的差別外:


function* ticketGenerator() {
}

當(dāng)你想要一個(gè)generator提供一個(gè)值然后暫停時(shí),你需要使用yield關(guān)鍵字。yield有點(diǎn)像是return關(guān)鍵字,因?yàn)樗鼈兌挤祷匾粋€(gè)值,但是函數(shù)在yield之后會(huì)進(jìn)入暫停狀態(tài)。


function* ticketGenerator() {
    yield 1;
    yield 2;
    yield 3;
}

在我們的例子中,我們定義了一個(gè)叫做ticketGenerator的迭代器。如果你向它請(qǐng)求一個(gè)值,它會(huì)返回1然后暫停。如果你再次向它發(fā)出一個(gè)請(qǐng)求,我們將得到2,然后是3。

當(dāng)你調(diào)用一個(gè)generator時(shí),它將返回一個(gè)迭代器對(duì)象。這個(gè)迭代器對(duì)象擁有一個(gè)叫做next的方法來(lái)幫助你重啟generator函數(shù)并得到下一個(gè)值。

next方法不僅返回值,它返回的對(duì)象具有兩個(gè)屬性:donevaluevalue是你獲得的值,done用來(lái)表明你的generator是否已經(jīng)停止提供值。

現(xiàn)在我們從我們的取號(hào)機(jī)中取一些號(hào)碼,


var takeANumber = ticketGenerator();
takeANumber.next();
// > { value: 1, done: false }
takeANumber.next();
// > { value: 2, done: false }
takeANumber.next();
// > { value: 3, done: false }
takeANumber.next();
// > { value: undefined, done: true }

現(xiàn)在我們的取號(hào)系統(tǒng)只能提供最多到3的號(hào)碼,這實(shí)在是沒(méi)什么用。我們想要讓它無(wú)線增加下去,因此我們來(lái)創(chuàng)建一個(gè)循環(huán)。


function* ticketGenerator() {
    for(var i=0; true; i++) {
        yield i;
    }
}

現(xiàn)在,如果這是一個(gè)普通的函數(shù),我們每次只會(huì)得到0。但是使用generator卻不一樣:


var takeANumber = ticketGenerator();
console.log(takeANumber.next().value); //0
console.log(takeANumber.next().value); //1
console.log(takeANumber.next().value); //2
console.log(takeANumber.next().value); //3
console.log(takeANumber.next().value); //4

每一次當(dāng)我們調(diào)用next()時(shí),generator執(zhí)行下一個(gè)循環(huán)迭代然后暫停。這意味著我們擁有一個(gè)可以無(wú)限向下運(yùn)行的generator。因?yàn)檫@個(gè)generator只是發(fā)生了暫停,你并沒(méi)有凍結(jié)你的程序。事實(shí)上,generator是一個(gè)創(chuàng)建無(wú)限循環(huán)的好方法。

影響generator的狀態(tài)

進(jìn)一步探究迭代迭代generator對(duì)象的話,next()實(shí)際上還有另一個(gè)用途。如果你給next傳遞一個(gè)值,它會(huì)被視為generator中的一個(gè)yield語(yǔ)句的結(jié)果來(lái)對(duì)待。

因此next是一個(gè)在generator運(yùn)行過(guò)程中向其傳遞信息的方式。我們將以此來(lái)修改我們的取號(hào)generator以便它能夠被重置到0。我們希望能在任何時(shí)間點(diǎn)來(lái)重置取號(hào)機(jī)。


function* ticketGenerator() {
    for(var i=0; true; i++) {
        var reset = yield i;
        if(reset) {
            i = -1;
        }
    }
}

正如你所看到的,如果yield返回了一個(gè)true然后我們將i設(shè)置為-1。那么for循環(huán)將會(huì)在循環(huán)的結(jié)尾將i增加1,因此下一次返回的i變成了0。

我們來(lái)看看實(shí)際情況如何:


var takeANumber = ticketGenerator();
console.log(takeANumber.next().value); //0
console.log(takeANumber.next().value); //1
console.log(takeANumber.next().value); //2
console.log(takeANumber.next(true).value); //0
console.log(takeANumber.next().value); //1

用generator替代回調(diào)函數(shù)

既然我們已經(jīng)學(xué)到了一些關(guān)于generator的知識(shí),現(xiàn)在讓我們來(lái)談?wù)刧enerator和回調(diào)函數(shù)。正如你所知道的,當(dāng)我們調(diào)用例如AJAX請(qǐng)求這樣的異步代碼時(shí)我們會(huì)使用回調(diào)函數(shù)。為了簡(jiǎn)單起見(jiàn)我們?cè)诶又卸x一個(gè)delay函數(shù)。

我們的delay函數(shù)將會(huì)是異步的 – 在指定的時(shí)間過(guò)后我們提供給delay的回調(diào)函數(shù)才會(huì)被執(zhí)行,然后delay會(huì)給你的回調(diào)函數(shù)傳遞一個(gè)字符串告訴它究竟沉睡了多久。

在此期間你的其余代碼將會(huì)繼續(xù)執(zhí)行下去。這就好像是進(jìn)行一個(gè)AJAX請(qǐng)求一樣 – 你發(fā)出請(qǐng)求,你的代碼繼續(xù)執(zhí)行,當(dāng)服務(wù)器返回一個(gè)結(jié)果時(shí)你的回調(diào)函數(shù)才執(zhí)行。

現(xiàn)在,我們來(lái)定義delay函數(shù),


function delay(time, callback){
    setTimeout(function(){
        callback("Slept for "+time);
    },time);
}

到目前為止,還沒(méi)有什么特別的東西?,F(xiàn)在我們來(lái)使用它來(lái)delay兩次。首先我們將delay 1000ms,然后當(dāng)delay結(jié)束后我們?cè)倭硗?em>delay 1200ms。


delay(1000,function(msg){
    console.log(msg);
    delay(1200,function(msg){
        console.log(msg);
    });
});
//...waits 1000ms
// > "Slept for 1000"
//...waits another 1200ms
// > "Slept for 1200

確保我們的兩個(gè)delay依次被調(diào)用的唯一方法就是確保第二個(gè)delay在第一個(gè)delay的回調(diào)函數(shù)中。

如果我們要依次delay 12次,我們將需要嵌套的調(diào)用12次delay函數(shù)。這時(shí)你就會(huì)碰到回調(diào)金字塔,代碼也變得丑陋不堪。

引入generator

Generator是解決回調(diào)地獄的有效方法之一。異步調(diào)用是很困難的事情,因?yàn)槲覀兊暮瘮?shù)不會(huì)等待異步調(diào)用完成,因此我們需要回調(diào)函數(shù)。

使用generator,我們可以讓我們的代碼進(jìn)行等待。無(wú)需嵌套回調(diào)函數(shù),我們可以使用generator確保當(dāng)異步調(diào)用在我們的generator函數(shù)運(yùn)行一下行代碼之前完成時(shí)暫停函數(shù)的執(zhí)行。

因此,如果我們可以在一個(gè)異步調(diào)用完成時(shí)暫停執(zhí)行,這就意味著我們可以依次調(diào)用delay函數(shù) – 就像delay函數(shù)是同步執(zhí)行的一樣。

我們應(yīng)該怎么做

首先,我們知道我們進(jìn)行異步調(diào)用的代碼需要在一個(gè)generator而不是一個(gè)一般的函數(shù)中進(jìn)行,因此我們來(lái)定義一個(gè)generator,


function* myDelayedMessages() {
    /* delay 1000 ms and print the result */
    /* delay 1200 ms and print the result */
}

接下來(lái)我們需要在我們的generator中調(diào)用delay。記住,delay接收一個(gè)回調(diào)函數(shù)。這個(gè)回調(diào)函數(shù)需要繼續(xù)我們的generator,但是我們現(xiàn)在還沒(méi)有一個(gè)generator因此我們先放上一個(gè)空函數(shù)。


function* myDelayedMessages() {
    console.log(delay(1000, function(){}));
    console.log(delay(1200, function(){}));
}

我們代碼依然是異步的。這是因?yàn)槲覀冞€沒(méi)有將放入任何的yield語(yǔ)句。Generator只是在它們看大一個(gè)yield語(yǔ)句時(shí)才暫停。


function* myDelayedMessages() {
    console.log(yield delay(1000, function(){}));
    console.log(yield delay(1200, function(){}));
}

我們現(xiàn)在已經(jīng)更接近了一點(diǎn)了。然而,如果我們運(yùn)行我們的generator什么也不會(huì)發(fā)生。因?yàn)闆](méi)有什么東西告訴它要向下運(yùn)行。

在這里你需要理解的最重要的概念是:generator需要在delay中的回調(diào)函數(shù)運(yùn)行完成后繼續(xù)往下運(yùn)行,這就是它們?nèi)绾沃罆和?yīng)該結(jié)束了的原因。

這意味著回調(diào)函數(shù)中的東西需要知道如何向前推動(dòng)generator。我們?cè)谄渲袀鬟f一個(gè)叫做resume的函數(shù)來(lái)為我們做這件事。記住我們現(xiàn)在還依然沒(méi)有定義resume。


function* myDelayedMessages(resume) {
    console.log(yield delay(1000, resume));
    console.log(yield delay(1200, resume));
}

OK,現(xiàn)在我們的generator將會(huì)接收一個(gè)resume函數(shù),這個(gè)函數(shù)將會(huì)向前推動(dòng)generator。

現(xiàn)在到了關(guān)鍵步驟了,我們?nèi)绾蝸?lái)編寫(xiě)resume,它又是怎么來(lái)了解我們的generator的。

如果你看看其他使用generator代替回調(diào)函數(shù)的例子,你會(huì)看到generator函數(shù)總是被另一個(gè)函數(shù)包裹著 – 通常是一個(gè)叫做run或者execute的函數(shù)。這些函數(shù)的目的有以下幾個(gè):

  • 接收一個(gè)generator作為參數(shù)
  • 使用這個(gè)generator來(lái)創(chuàng)建一個(gè)新的generator迭代器對(duì)象,我們將調(diào)用它的next方法
  • 創(chuàng)建一個(gè)resume函數(shù)來(lái)使用這個(gè)generator迭代器對(duì)象來(lái)推進(jìn)generator
  • resume函數(shù)傳遞給這個(gè)generator以便generator能夠訪問(wèn)resume
  • 在最開(kāi)始時(shí)調(diào)用next()函數(shù),以便我們的代碼在碰到第一個(gè)yield之前開(kāi)始執(zhí)行

現(xiàn)在我們來(lái)創(chuàng)建run函數(shù),


function run(generatorFunction) {
    var generatorItr = generatorFunction(resume);
    function resume(callbackValue) {
        generatorItr.next(callbackValue);
    }
    generatorItr.next()
}

現(xiàn)在我們有了一個(gè)能夠接收一個(gè)generator函數(shù)的函數(shù),并為它傳遞了一個(gè)了解如何推進(jìn)generator迭代器對(duì)象的函數(shù)。

注意到我們?cè)?code>resume函數(shù)中用到了next的第二個(gè)特性。resume是被傳遞給delay的回調(diào)函數(shù),因此它接收delay函數(shù)提供的值。resume將這個(gè)值傳遞給next,因此yield語(yǔ)句的結(jié)果實(shí)際上是我們異步函數(shù)的結(jié)果!

我們現(xiàn)在要做的只是用run包裹上我們的generator函數(shù),然后我們就能看到以下結(jié)果,


run(function* myDelayedMessages(resume) {
    console.log(yield delay(1000, resume));
    console.log(yield delay(1200, resume));
});
//...waits 1000ms
// > "Slept for 1000"
//...waits 1200ms
// > "Slept for 1200"

現(xiàn)在,你能看到我們調(diào)用delay兩次,并沒(méi)有使用嵌套回調(diào)函數(shù)。如果你依然看到疑惑,我們現(xiàn)在概括的來(lái)講述以下究竟發(fā)生了什么:

  • run接收了我們的generator并創(chuàng)建了一個(gè)resume函數(shù)
  • run創(chuàng)建了一個(gè)generator迭代器對(duì)象(我們?cè)谒厦嬲{(diào)用next方法),提供了resume函數(shù)。接著它推動(dòng)了generator迭代器向前運(yùn)行。
  • 我們的generator碰到了第一個(gè)yield語(yǔ)句并且調(diào)用delay。接著這個(gè)generator暫停。
  • delay在1000ms之后完成然后調(diào)用resume。
  • resume告訴我們的generator進(jìn)行下一步。它將結(jié)果傳遞給delay以便console能夠?qū)⑺蛴〕鰜?lái)。
  • 我們的generator碰到了第二個(gè)yield,調(diào)用delay然后再次暫停。 delay等待1200ms之后調(diào)用resume回調(diào)函數(shù)。
  • resume再次推進(jìn)generator。
  • 再也沒(méi)有yield的調(diào)用,這個(gè)generator完成執(zhí)行。

結(jié)論

我們已經(jīng)成功的使用generator替代了回調(diào)嵌套方法??偨Y(jié)一下,使用generator替代回調(diào)函數(shù)要包含以下幾個(gè)步驟:

  • 創(chuàng)建一個(gè)run函數(shù)來(lái)接受一個(gè)generator,并為這個(gè)generator提供resume函數(shù)。
  • 創(chuàng)建一個(gè)resume函數(shù)來(lái)推進(jìn)generator,然后在resume被異步函數(shù)調(diào)用時(shí)將這個(gè)resume函數(shù)傳遞給generator。
  • resume作為回調(diào)傳遞給我們所有的異步回調(diào)函數(shù)。這些異步函數(shù)在完成時(shí)執(zhí)行resume,這使得我們的generator在每個(gè)異步調(diào)用完成之時(shí)僅僅向前一步。

雖然generator究竟是不是一個(gè)處理回調(diào)地獄的好方法還在討論之中,但是它確實(shí)是一個(gè)加強(qiáng)你對(duì)ES6中g(shù)enerator和迭代器理解的練習(xí)。如果你在尋找一些不需要用到ES6的處理嵌套回調(diào)函數(shù)的方法,可以考慮promises。

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)