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

資訊專欄INFORMATION COLUMN

ES6中的異步編程:Generators函數(shù)+Promise:最強(qiáng)大的異步處理方式

Taonce / 3578人閱讀

摘要:更好的異步編程上面的方法可以適用于那些比較簡(jiǎn)單的異步工作流程。小結(jié)的組合目前是最強(qiáng)大,也是最優(yōu)雅的異步流程管理編程方式。

訪問原文地址

generators主要作用就是提供了一種,單線程的,很像同步方法的編程風(fēng)格,方便你把異步實(shí)現(xiàn)的那些細(xì)節(jié)藏在別處。這讓我們可以用一種很自然的方式書寫我們代碼中的流程和狀態(tài)邏輯,不再需要去遵循那些奇怪的異步編程風(fēng)格。

換句話說,通過將我們generator邏輯中的一些值的運(yùn)算操作和和異步處理(使用generators的迭代器iterator)這些值實(shí)現(xiàn)細(xì)節(jié)分開來寫,我們可以非常好的把性能處理和業(yè)務(wù)關(guān)注點(diǎn)給分開。

結(jié)果呢?所有那些強(qiáng)大的異步代碼,將具備跟同步編程一樣的可讀性和可維護(hù)性。

那么我們將如何完成這些壯舉?

一個(gè)簡(jiǎn)單的異步功能

先從這個(gè)非常的簡(jiǎn)單的sample代碼開始,目前generators并不需要去額外的處理一些這段代碼還沒有了的異步功能。

舉例下,加入現(xiàn)在的異步代碼已經(jīng)是這樣的了:

function makeAjaxCall(url, cb) {
    //do some ajax fun
    //call cb(result) when complete
}

makeAjaxCall("http://some.url.1", function(result1) {
    var data = JSON.parse(result1);
    
    makeAjaxCall("http://some.url.2/?id="+data.id, function(result2) {
        var resp = JSON.parse(result2);
        console.log("The value you asked for: "+ resp.value);
    });
});

用一個(gè)generator(不添加任何decoration)去重新實(shí)現(xiàn)一遍,代碼看這里:

function request(url) {
    // this is where we"re hiding the asynchronicity,
    // away from the main code of our generator
    // it.next() 是generators的迭代器
    makeAjaxCall(url, function(response) {
        it.next(response);
    });
}

function *main() {
    var result1 = yield request("http://some.url.1");
    var data = JSON.parse(result1);
    
    var result2 = yield request("http://some.url.2/?id="+data.id);
    var resp = JSON.parse(result2);
    console.log("The value you asked for: "+ resp.value);
}

var it = main();
it.next(); //啟動(dòng)所有請(qǐng)求

讓我們捋一下這是如何工作的

request(..)函數(shù)對(duì)makeAjaxCall(..)做了基本封裝,讓數(shù)據(jù)請(qǐng)求的回調(diào)函數(shù)中調(diào)用generator的iterator的next(...)方法。

先來看調(diào)用request(".."),你會(huì)發(fā)現(xiàn)這里根本沒有return任何值(換句話來說,這是undefined)。這沒關(guān)系,但是它跟我們?cè)谶@篇文章后面會(huì)討論到的方法做對(duì)比是很重要的:我需要有效的在這里使用yield undefined.

這時(shí)我們來調(diào)用yield(這時(shí)還是一個(gè)undefined值),其實(shí)除了暫停一下我們的generators之外沒有做別的了。它將等待知道下次再調(diào)用it.next(..) 才恢復(fù),我們隊(duì)列已經(jīng)把它在安排在(作為一個(gè)回調(diào))Ajax請(qǐng)求結(jié)束的時(shí)候。

但是當(dāng)yield表達(dá)式執(zhí)行返回結(jié)果后我們做了什么?我們把返回值賦值給了result1.。那為什么yield會(huì)有從第一個(gè)Ajax請(qǐng)求返回的值?

這是因?yàn)楫?dāng)it.next(..)在Ajax的callback中調(diào)用的是偶,它傳遞Ajax的返回結(jié)果。這說明這個(gè)返回值發(fā)送到我們的generators時(shí),已經(jīng)中間那句result1 = yield .. 給暫停下來了。

這個(gè)真的很酷很強(qiáng)大。實(shí)質(zhì)上看,result1 = yield request(..)這句是請(qǐng)求數(shù)據(jù),但是它完全把異步邏輯在我們面前藏起來了,至少不需要我們?cè)谶@里考慮這部分異步邏輯,它通過yield的暫停能力隱藏了異步邏輯,同時(shí)把generator恢復(fù)邏輯的功能分離到下一個(gè)yield函數(shù)中。這就讓我們的主要邏輯看上去很像一個(gè)同步請(qǐng)求方法。

第二句表達(dá)式result2 = yield result(..)也基本一樣的作用,它將pauses與resumes傳進(jìn)去,輸出一個(gè)我們請(qǐng)求的值,也根本不需要對(duì)異步操作擔(dān)心。

當(dāng)然,因?yàn)閥ield的存在,這里會(huì)有一個(gè)微妙的提示,在這個(gè)點(diǎn)上會(huì)發(fā)生一些神奇的事情(也稱異步)。但是跟噩夢(mèng)般的嵌套回調(diào)地獄(或者是promise鏈的API開銷)相比,yield語句只是需要一個(gè)很小的語法開銷。

上面的代碼總是啟動(dòng)一個(gè)異步Ajax請(qǐng)求,但是如果沒有做會(huì)發(fā)生什么?如果我們后來更改了我們程序中先前(預(yù)先請(qǐng)求)的Ajax返回的數(shù)據(jù),該怎么辦?或者我們的程序的URL路由系統(tǒng)通過其他一些復(fù)雜的邏輯,可以立即滿足Ajax請(qǐng)求,這時(shí)就可以不需要fetch數(shù)據(jù)從服務(wù)器了。

這樣,我們可以把request(..)代碼稍微修改一下

var cache = {};

function request(url) {
    if(cache[url]) {
        // defer cache里面的數(shù)據(jù)對(duì)現(xiàn)在來說是已經(jīng)足夠了
        // 執(zhí)行下面
        setTimeout(function() {
            it.next(cache[url])
        }, 0);
    }
    else {
        makeAjaxCall(url, function(resp) {
            cache[url] = resp;
            it.next(resp);
        })
    }
}

注意:一句很奇妙、神奇的setTimeout(..0)放在了當(dāng)緩存中已經(jīng)請(qǐng)求過數(shù)據(jù)的處理邏輯中。如果我們立即調(diào)用it.next(...),這樣會(huì)發(fā)生一個(gè)error,這是因?yàn)間enerator還沒有完成paused操作。我們的函數(shù)首先要完全調(diào)用request(..),這時(shí)才會(huì)啟動(dòng)yield的暫停。因此,我們不能立即在request(..)中立即調(diào)用it.next(...),這是因?yàn)檫@時(shí)generator仍然在運(yùn)行(yield并沒有執(zhí)行)。但是我們可以稍后一點(diǎn)調(diào)用it.next(...),等待現(xiàn)在的線程執(zhí)行完畢,這就是setTimeout(..0)這句有魔性的代碼放在這里的意義。我們稍后還會(huì)有一個(gè)更好的解決辦法。

現(xiàn)在,我們的generator代碼并不需要發(fā)生任何變化:

var restult1 = yield request("http://some.url.1");
var data = JSON.parse(result1);
...

看到?jīng)]?我們的generator邏輯(也就是我們的流程邏輯)即使增加了緩存處理功能后,仍不需要發(fā)生任何改變。

*main()中的代碼還是只需要請(qǐng)求數(shù)據(jù)后暫停,之后等到數(shù)據(jù)返回后順序執(zhí)行下去。在我們當(dāng)前的情況下,‘暫?!赡芟鄬?duì)要長(zhǎng)一些(做一個(gè)服務(wù)器的請(qǐng)求,大約要300~800ms),或者他可以幾乎立即返回(走setTimeout的邏輯),但是我們的流程邏輯完全不需要關(guān)心這些。

這就是將異步編程抽象成更小細(xì)節(jié)的真正力量。

更好的異步編程

上面的方法可以適用于那些比較簡(jiǎn)單的異步generator工作流程。但是它將很快收到限制,因此我們需要一些更強(qiáng)大的異步機(jī)制與我們的generator來合作,這樣才可以發(fā)揮出更強(qiáng)大的功能。那是什么機(jī)制:Promise。

早先的Ajax實(shí)例總是會(huì)收到嵌套回調(diào)的困擾,問題如下:

1.沒有明確的方法來處理請(qǐng)求error。我們都知道,Ajax請(qǐng)求有時(shí)是會(huì)失敗的,這時(shí)我們需要使用generator中的it.throw(...),同時(shí)還需要使用try...catch來處理請(qǐng)求錯(cuò)誤時(shí)的邏輯。但是這更多是一些在后臺(tái)(我們那些在iterator中的代碼)手動(dòng)的工作。我需要一些可以服用的方法,放在我們自己代碼的generator中。

2.假如makeAjaxCall(..)這段代碼不在我們的控制下了,或者他需要多次調(diào)用回調(diào),又或者它同時(shí)返回success與error,等等。這時(shí)我們的generator的會(huì)變得亂七八糟(返回error實(shí)現(xiàn),出現(xiàn)異常值,等等)。控制以及防止發(fā)生這類問題是需要花費(fèi)大量的手工時(shí)間的,而且一點(diǎn)也不能即插即用。

3.通常我需要執(zhí)行并行執(zhí)行任務(wù)(比如同時(shí)做2個(gè)Ajax請(qǐng)求)。由于generator yield機(jī)制都是逐步暫停,無法在同時(shí)運(yùn)行另一個(gè)或多個(gè)任務(wù),他的任務(wù)必須一個(gè)一個(gè)的按順序執(zhí)行。因此,這不是太容易在一個(gè)generator中去操作多任務(wù),我們只能默默的在背后手?jǐn)]大量的代碼。

就像你看到的,所有的問題都被解決了。但是沒人愿意每次都去反復(fù)的去實(shí)現(xiàn)一遍這些方法。我們需要一種更強(qiáng)大的模式,專門設(shè)計(jì)出一個(gè)可信賴的,可重用的基于generator異步編程的解決方法。

什么模式?把promise與yield結(jié)合,使得可以在執(zhí)行完成后恢復(fù)generator的流程。

讓我們稍微用promise修改下request(..),讓yield返回一個(gè)promise。

function request(url) {
    //現(xiàn)在返回一個(gè)promise了
    return new Promise( function(resolve, reject) {
        makeAjaxCall(url, resolve);
    });
}

request(..)現(xiàn)在由一個(gè)promise構(gòu)成,當(dāng)Ajax請(qǐng)求完成后會(huì)返回這個(gè)promise,但是然后呢?

我們需要控制generator的iterator,它將接受到y(tǒng)ield返回的那個(gè)promise,同時(shí)通過next(...)恢復(fù)generator運(yùn)行,并把他們傳遞下去,我增加了一個(gè)runGenerator(...)方法來做這件事。

//比較簡(jiǎn)單,沒有error事件處理
funtion runGenerator(g) {
    var it = g(), retl
    
    //異步iterator遍歷generator
    (function iterate(val) {
        //返回一個(gè)promise
        ret = it.next(val);
        
        if(!ret.done) {
            if("then" in ret.value) {
                //等待接收promise
                ret.value.then(iterate);
            }
            //獲取立即就有的數(shù)據(jù),不是promise了
            else {
                //避免同步操作
                setTimeout(function() {
                    iterate(ret.value);
                }, 0);
            }
        }
    })();
}

關(guān)鍵點(diǎn) :

自動(dòng)初始化generator(直接創(chuàng)建它的iterator),并且異步遞將他一直運(yùn)行到結(jié)束(當(dāng)done:true就不在執(zhí)行)

如果Promise被返回出來,這時(shí)我們就等待到執(zhí)行then(...)方法的時(shí)候再處理。

如果是可以立即返回的數(shù)據(jù),我們直接把數(shù)據(jù)返回給generator讓他直接去執(zhí)行下一步。

runGenerator( function *main(){
    var result1 = yield request( "http://some.url.1" );
    var data = JSON.parse( result1 );

    var result2 = yield request( "http://some.url.2?id=" + data.id );
    var resp = JSON.parse( result2 );
    console.log( "The value you asked for: " + resp.value );
} );

等一下,現(xiàn)在的generator跟原先的完全一樣嘛。盡管我們改用了promise,但是yield方法不需要有什么變化,因?yàn)槲覀儼涯切┻壿嫸紡奈覀兊牧鞒坦芾碇蟹蛛x出去了。

盡管現(xiàn)在的yield是返回一個(gè)promise了,并把這個(gè)promise傳遞給下一個(gè)it.next(..),但是result1 = yield request(..)這句得到的值跟以前還是一樣的。

我們現(xiàn)在開始用promise來管理generator中的異步代碼,這樣我們就解決掉所有使用回調(diào)函數(shù)方法中會(huì)出現(xiàn)的反轉(zhuǎn)/信任問題。由于我們用了generator+promise的方法,我們不需要增加任何邏輯就解決掉了以上所有問題

我們可以很容易的增加一個(gè)error異常處理。雖然不在runGenerator(...)中,但是很容易從一個(gè)promise中監(jiān)聽error,并它他們的邏輯寫在it.throw(..)里面,這時(shí)我們就可以用上try..catch`方法在我們的generator代碼中去獲取和管理erros了。

我們得到到所有的 control/trustability,完全不需要增加代碼。

promise有著很強(qiáng)的抽象性,讓我們可以實(shí)現(xiàn)一些多任務(wù)的并行操作。

比如:`Promise.all([ .. ])`就可以并行執(zhí)行一個(gè)promise數(shù)組,yield雖然只能拿到一個(gè)promise,但是這個(gè)是所有子promise執(zhí)行完畢之后的集合數(shù)組。

先讓我們看下error處理的代碼:

function request(url) {
    return new Promise( function(resolve, reject) {
        //第一個(gè)參數(shù)是error
        makeAjaxCall(url, function(err, text) {
            if(err) reject(err);
            else resolve(text);
        });
    });
}

runGenerator(function *main() {
    try {
        var result1 = yield request("http://some.url.1");
    }
    catch(err) {
        console.log("Error:" + err);
        retrun;
    }
    var data = JSON.parse(result1);
    
    try{
        var result2 = yield request("http://some.url.2?id="+data.id);
    }
    catch(err) {
        console.log("Error:" + err);
        retrun;
    }
    var resp = JSON.parse(result2);
    console.log("The value you asked for: " + resp.value);
});

如果執(zhí)行url的fetch時(shí)promise被reject(請(qǐng)求失敗,或者異常)了,promise會(huì)給generator拋出一個(gè)異常,通過try..catch語句可以獲取到了。

現(xiàn)在,我們讓promise來處理更復(fù)雜的異步操作:

function request(url) {
    return new Promise( function(resolve, reject) {
        makeAjax(url, resolve);
    })
    //獲取到返回的text值后,做一些處理。
    .then( function(text) {
        
            //如果我們拿到的是一個(gè)url就把text提前出來在返回
            if(/^http?://.+/.text(text)) {
                return request(text);            }
            //如果我們就是要一個(gè)text,直接返回
            else {
                return text;
            }
    });
}

runGenerator (function *main() {
    var search_terms = yield Promise.all([
        request( "http://some.url.1" ),
     request( "http://some.url.2" ),
     request( "http://some.url.3" )
    ]);

    var search_results = yield request(
        "http://some.url.4?search="+search_terms.join("+")
    );
    var resp = JSON.parse(search_results);
    
    console.log("Search results:"+resp.value);
});

Promise.all([ .. ]) 里面放了3個(gè)子promise,主promise完成后就會(huì)在runGenerator中恢復(fù)generator。子promise拿到的是一天重定向的url,我們會(huì)把它丟給下一個(gè)request請(qǐng)求,然后獲取到最終數(shù)據(jù)。

任何復(fù)雜的異步功能都可以被promise搞定,而且你還可以用generator把這些流程寫的像同步代碼一樣。只要你讓yield返回一個(gè)promise。

ES7 async

現(xiàn)在可以稍微提下ES7了,它更像把runGenerator(..)這個(gè)異步執(zhí)行邏輯做了一層封裝。

async funtion main() {
    var result1 = await request("http://some.url.1");
    var data = JSON.parse(result1);
    
    var result2 = await request("http://some.url.2?id="+data.id);
    var resp = JSON.parse(result2);
    console.log( "The value you asked for: " + resp.value );
}

main();

我們直接調(diào)用main()就可以執(zhí)行完所有的流程,不需要調(diào)用next,也不需要去實(shí)現(xiàn)runGenerator(..)之類的來管理promise邏輯。只需要把yield關(guān)鍵詞換成await就可以告訴異步方法,我們?cè)谶@里需要等到一個(gè)promise后才會(huì)接著執(zhí)行。

有了這些原生的語法支持,是不是很酷。

小結(jié)

generator + yielded promise(s)的組合目前是最強(qiáng)大,也是最優(yōu)雅的異步流程管理編程方式。通過封裝一層流執(zhí)行邏輯,我們可以自動(dòng)的讓我們的generator執(zhí)行結(jié)束,并且還可以像處理同步邏輯一樣管理error事件。

在ES7中,我們甚至連這一層封裝都不需要寫了,變得更方便

參考

Asynchronous calls with ES6 generators

[Javascript] Promise, generator, async與ES6

The Hidden Power of ES6 Generators: Observable Async Flow Control

Going Async With ES6 Generators

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

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

相關(guān)文章

  • ES6中的異步編程Generators函數(shù)(一)

    摘要:由于可以使用語句來暫停異步操作,這讓異步編程的代碼,很像同步數(shù)據(jù)流方法一樣。該臨時(shí)函數(shù)就叫做函數(shù)。下面就是簡(jiǎn)單的函數(shù)轉(zhuǎn)換器。 訪問原文地址 對(duì)ES6的generators的介紹分為3個(gè)部分 第一部分base介紹及使用 第二部分基于generators和Promise實(shí)現(xiàn)最強(qiáng)大的異步處理邏輯 概述 Generator函數(shù)是協(xié)程在ES6的實(shí)現(xiàn),用來做異步流程的封裝,最大特點(diǎn)就是可以交出...

    ztyzz 評(píng)論0 收藏0
  • JavaScript 異步編程的四種方式

    摘要:異步編程是每個(gè)使用編程的人都會(huì)遇到的問題,無論是前端的請(qǐng)求,或是的各種異步。本文就來總結(jié)一下常見的四種處理異步編程的方法。利用一種鏈?zhǔn)秸{(diào)用的方法來組織異步代碼,可以將原來以回調(diào)函數(shù)形式調(diào)用的代碼改為鏈?zhǔn)秸{(diào)用。 異步編程是每個(gè)使用 JavaScript 編程的人都會(huì)遇到的問題,無論是前端的 ajax 請(qǐng)求,或是 node 的各種異步 API。本文就來總結(jié)一下常見的四種處理異步編程的方法。...

    microelec 評(píng)論0 收藏0
  • ES6中文手冊(cè)、ES6 Cheatsheet

    摘要:盲目使用替換后可能會(huì)導(dǎo)致預(yù)期意外的結(jié)果。在中,許多種方法來處理函數(shù)的參數(shù)默認(rèn)值,參數(shù)數(shù)量,參數(shù)命名。此外,處理后的值,無論是解決還是拒絕的結(jié)果值,都是不可改變的。 這是一個(gè) ES2015(ES6) 的Cheatsheet,其中包括提示、小技巧、最佳實(shí)踐和一些代碼片段,幫助你完成日復(fù)一日的開發(fā)工作。 Table of Contents var 與 let / const 聲明 代碼執(zhí)行...

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

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

    charles_paul 評(píng)論0 收藏0
  • 通過ES6 Generator函數(shù)實(shí)現(xiàn)異步流程

    摘要:換句話說,我們很好的對(duì)代碼的功能關(guān)注點(diǎn)進(jìn)行了分離通過將使用消費(fèi)值得地方函數(shù)中的邏輯和通過異步流程來獲取值迭代器的方法進(jìn)行了有效的分離。但是現(xiàn)在我們通過來管理代碼的異步流程部分,我們解決了回調(diào)函數(shù)所帶來的反轉(zhuǎn)控制等問題。 本文翻譯自 Going Async With ES6 Generators 由于個(gè)人能力知識(shí)有限,翻譯過程中難免有紕漏和錯(cuò)誤,還望指正Issue ES6 Gener...

    劉厚水 評(píng)論0 收藏0

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

0條評(píng)論

Taonce

|高級(jí)講師

TA的文章

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