摘要:更好的異步編程上面的方法可以適用于那些比較簡(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
摘要:由于可以使用語句來暫停異步操作,這讓異步編程的代碼,很像同步數(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)就是可以交出...
摘要:異步編程是每個(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é)一下常見的四種處理異步編程的方法。...
摘要:盲目使用替換后可能會(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í)行...
摘要:注是先前版本處理異步函數(shù)的方式,通過可以將異步函數(shù)封裝成,傳入普通參數(shù)后形成僅需要參數(shù)的偏函數(shù),以此簡(jiǎn)化調(diào)用代碼目前中的偏函數(shù)已經(jīng)被無情地化了。 前幾天研究了TJ的koa/co4.x和一系列koa依賴的源碼,在知乎上做出了人生首次回答(而且我真得再也不想去知乎回答技術(shù)問題了_(:з」∠)_),因此把文字搬到這里。 ES2015 Generator/Yield 關(guān)于Generator...
摘要:換句話說,我們很好的對(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...
閱讀 3938·2021-09-09 09:33
閱讀 1802·2021-09-06 15:14
閱讀 1939·2019-08-30 15:44
閱讀 3091·2019-08-29 18:36
閱讀 3781·2019-08-29 16:22
閱讀 2106·2019-08-29 16:21
閱讀 2549·2019-08-29 15:42
閱讀 1662·2019-08-29 11:00