摘要:如果我們只有一個(gè)異步操作,用回調(diào)函數(shù)來(lái)處理是完全沒(méi)有任何問(wèn)題的。事件監(jiān)聽使用事件監(jiān)聽的方式番禺廣州上述代碼需要實(shí)現(xiàn)一個(gè)事件監(jiān)聽器。只處理對(duì)象廣州番禺函數(shù)將函數(shù)的自動(dòng)執(zhí)行器,改在語(yǔ)言層面提供,不暴露給用戶。
概論
由于 JavaScript 是一門單線程執(zhí)行的語(yǔ)言,所以在我們處理耗時(shí)較長(zhǎng)的任務(wù)時(shí),異步編程就顯得尤為重要。
js 處理異步操作最傳統(tǒng)的方式是回調(diào)函數(shù),基本上所有的異步操作都可以用回調(diào)函數(shù)來(lái)處理;
為了使代碼更優(yōu)雅,人們又想到了用事件監(jiān)聽、發(fā)布/訂閱模式和 Promise 等來(lái)處理異步操作;
之后在 ES2015 語(yǔ)言標(biāo)準(zhǔn)中終于引入了Promise,從此瀏覽器原生支持 Promise ;
此外,ES2015 中的生成器generator因其中斷/恢復(fù)執(zhí)行和傳值等優(yōu)秀功能也被人們用于異步處理;
之后,ES2017 語(yǔ)言標(biāo)準(zhǔn)又引入了更優(yōu)秀的異步處理方法async/await......
為了更直觀地發(fā)現(xiàn)這些異步處理方式的優(yōu)勢(shì)和不足,我們將分別使用不同的方式解決同一個(gè)異步問(wèn)題。
問(wèn)題:假設(shè)我們需要用原生 XMLHttpRequest 獲取兩個(gè) json 數(shù)據(jù) —— 首先異步獲取廣州的天氣,等成功后再異步獲取番禺的天氣,最后一起輸出獲取到的兩個(gè) json 數(shù)據(jù)。
前提:假設(shè)我們已經(jīng)了解了Promise,generator和async。
我們首先用最傳統(tǒng)的回調(diào)函數(shù)來(lái)處理:
var xhr1 = new XMLHttpRequest(); xhr1.open("GET", "https://www.apiopen.top/weatherApi?city=廣州"); xhr1.send(); xhr1.onreadystatechange = function() { if(this.readyState !== 4) return; if(this.status === 200) { data1 = JSON.parse(this.response); var xhr2 = new XMLHttpRequest(); xhr2.open("GET", "https://www.apiopen.top/weatherApi?city=番禺"); xhr2.send(); xhr2.onreadystatechange = function() { if(this.readyState !== 4) return; if(this.status === 200) { data2 = JSON.parse(this.response); console.log(data1, data2); } } } };
優(yōu)點(diǎn):簡(jiǎn)單、方便、實(shí)用。
缺點(diǎn):易形成回調(diào)函數(shù)地獄。如果我們只有一個(gè)異步操作,用回調(diào)函數(shù)來(lái)處理是完全沒(méi)有任何問(wèn)題的。如果我們?cè)诨卣{(diào)函數(shù)中再嵌套一個(gè)回調(diào)函數(shù),問(wèn)題也不大。但是如果我們要嵌套很多個(gè)回調(diào)函數(shù),問(wèn)題就很大了,因?yàn)槎鄠€(gè)異步操作形成了強(qiáng)耦合,代碼將亂作一團(tuán),無(wú)法管理。這種情況被稱為"回調(diào)函數(shù)地獄"(callback hell)。
使用事件監(jiān)聽的方式:
var events = new Events(); events.addEvent("done", function(data1) { var xhr = new XMLHttpRequest(); xhr.open("GET", "https://www.apiopen.top/weatherApi?city=番禺"); xhr.send(); xhr.onreadystatechange = function() { if(this.readyState !== 4) return; if(this.status === 200) { data1 = JSON.parse(data1); var data2 = JSON.parse(this.response); console.log(data1, data2); } } }); var xhr = new XMLHttpRequest(); xhr.open("GET", "https://www.apiopen.top/weatherApi?city=廣州"); xhr.send(); xhr.onreadystatechange = function() { if(this.readyState !== 4) return; if(this.status === 200) { events.fireEvent("done", this.response); } };
上述代碼需要實(shí)現(xiàn)一個(gè)事件監(jiān)聽器 Events。
優(yōu)點(diǎn):與回調(diào)函數(shù)相比,事件監(jiān)聽方式實(shí)現(xiàn)了代碼的解耦,將兩個(gè)回調(diào)函數(shù)分離了開來(lái),更方便進(jìn)行代碼的管理。
缺點(diǎn):使用起來(lái)不方便,每次都要手動(dòng)地綁定和觸發(fā)事件。
而發(fā)布/訂閱模式與其類似,就不多說(shuō)了。
使用 ES6 Promise 的方式:
new Promise(function(resolve, reject) { const xhr = new XMLHttpRequest(); xhr.open("GET", "https://www.apiopen.top/weatherApi?city=廣州"); xhr.send(); xhr.onreadystatechange = function() { if(this.readyState !== 4) return; if(this.status === 200) return resolve(this.response); reject(this.statusText); }; }).then(function(value) { const xhr = new XMLHttpRequest(); xhr.open("GET", "https://www.apiopen.top/weatherApi?city=番禺"); xhr.send(); xhr.onreadystatechange = function() { if(this.readyState !== 4) return; if(this.status === 200) { const data1 = JSON.parse(value); const data2 = JSON.parse(this.response); console.log(data1, data2); } }; });
優(yōu)點(diǎn):使用Promise的方式,我們成功地將回調(diào)函數(shù)嵌套調(diào)用變成了鏈?zhǔn)秸{(diào)用,與前兩種方式相比邏輯更強(qiáng),執(zhí)行順序更清楚。
缺點(diǎn):代碼冗余,異步操作都被包裹在Promise構(gòu)造函數(shù)和then方法中,主體代碼不明顯,語(yǔ)義變得不清楚。
接下來(lái),我們使用 generator 和回調(diào)函數(shù)來(lái)實(shí)現(xiàn)。
首先用一個(gè) generator function 封裝異步操作的邏輯代碼:
function* gen() { const data1 = yield getJSON_TH("https://www.apiopen.top/weatherApi?city=廣州"); const data2 = yield getJSON_TH("https://www.apiopen.top/weatherApi?city=番禺"); console.log(data1, data2); }
看了這段代碼,是不是感覺(jué)它很直觀、很優(yōu)雅。實(shí)際上,除去星號(hào)和yield關(guān)鍵字,這段代碼就變得和同步代碼一樣了。
當(dāng)然,只有這個(gè) gen 函數(shù)是沒(méi)有用的,直接執(zhí)行它只會(huì)得到一個(gè)generator對(duì)象。我們需要用它返回的 generator 對(duì)象來(lái)恢復(fù)/暫停 gen 函數(shù)的執(zhí)行,同時(shí)傳遞數(shù)據(jù)到 gen 函數(shù)中。
用getJSON_TH函數(shù)封裝異步操作的主體代碼:
function getJSON_TH(url) { return function(fn) { const xhr = new XMLHttpRequest(); xhr.open("GET", url); xhr.responseType = "json"; xhr.setRequestHeader("Accept", "application/json"); xhr.send(); xhr.onreadystatechange = function() { if(this.readyState !== 4) return; let err, data; if(this.status === 200) { data = this.response; } else { err = new Error(this.statusText); } fn(err, data); } } }
有的同學(xué)可能覺(jué)得直接給getJSON_TH函數(shù)傳入 url 和 fn 兩個(gè)參數(shù)不就行了嗎,為什么非要返回一個(gè)函數(shù)。其實(shí)這正是奧妙所在,getJSON_TH函數(shù)返回的函數(shù)是一個(gè)Thunk函數(shù),它只接收一個(gè)回調(diào)函數(shù)作為參數(shù)。通過(guò)Thunk函數(shù)或者說(shuō)Thunk函數(shù)的回調(diào)函數(shù),我們可以在 gen 函數(shù)外部向其內(nèi)部傳入數(shù)據(jù),同時(shí)恢復(fù) gen 函數(shù)的執(zhí)行。在 node.js 中,我們可以通過(guò) Thunkify 模塊將帶回調(diào)參數(shù)的函數(shù)轉(zhuǎn)化為 Thunk 函數(shù)。
接下來(lái),我們手動(dòng)執(zhí)行 gen 函數(shù):
const g = gen(); g.next().value((err, data) => { if(err) return g.throw(err); g.next(data).value((err, data) => { if(err) return g.throw(err); g.next(data); }) });
其中,g.next().value 就是 gen 函數(shù)中yield輸出的值,也就是我們之前提到的Thunk函數(shù),我們?cè)谒幕卣{(diào)函數(shù)中,通過(guò) g.next(data) 方法將 data 傳給 gen 函數(shù)中的 data1,并且恢復(fù) gen 函數(shù)的執(zhí)行(將 gen 函數(shù)的執(zhí)行上下文再次壓入調(diào)用棧中)。
方便起見(jiàn),我們還可以將自動(dòng)執(zhí)行 gen 函數(shù)的操作封裝起來(lái):
function run(gen) { const g = gen(); function next(err, data) { if(err) return g.throw(err); const res = g.next(data); if(res.done) return; res.value(next); } next(); } run(gen);
優(yōu)點(diǎn):generator 方式使得異步操作很接近同步操作,十分的簡(jiǎn)潔明了。另外,gen 執(zhí)行 yield 語(yǔ)句時(shí),只是將執(zhí)行上下文暫時(shí)彈出,并不會(huì)銷毀,這使得上下文狀態(tài)被保存。
缺點(diǎn):流程管理不方便,需要一個(gè)執(zhí)行器來(lái)執(zhí)行 generator 函數(shù)。
除了Thunk函數(shù),我們還可以借助Promise對(duì)象來(lái)執(zhí)行 generator 函數(shù)。
同樣優(yōu)雅的邏輯代碼:
function* gen() { const data1 = yield getJSON_PM("https://www.apiopen.top/weatherApi?city=廣州"); const data2 = yield getJSON_PM("https://www.apiopen.top/weatherApi?city=番禺"); console.log(data1, data2); }
getJSON_PM函數(shù)返回一個(gè) Promise 對(duì)象:
function getJSON_PM(url) { return new Promise((resolve, rejext) => { const xhr = new XMLHttpRequest(); xhr.open("GET", url); xhr.responseType = "json"; xhr.setRequestHeader("Accept", "application/json"); xhr.send(); xhr.onreadystatechange = function() { if(this.readyState !== 4) return; if(this.status === 200) return resolve(this.response); reject(new Error(this.statusText)); }; }); }
手動(dòng)執(zhí)行 generator 函數(shù):
const g = gen(); g.next().value.then(data => { g.next(data).value.then(data => g.next(data), err => g.throw(err)); }, err => g.throw(err));
自動(dòng)執(zhí)行 generator 函數(shù):
function run(gen) { const g = gen(); function next(data) { const res = g.next(data); if(res.done) return; res.value.then(next); } next(); } run(gen);generator + co 模塊
node.js 中的co模塊是一個(gè)用來(lái)自動(dòng)執(zhí)行generator函數(shù)的模塊,它的入口是一個(gè)co(gen)函數(shù),它預(yù)期接收一個(gè) generator 對(duì)象或者 generator 函數(shù)作為參數(shù),返回一個(gè)Promise對(duì)象。
在參數(shù) gen 函數(shù)中,yield語(yǔ)句預(yù)期接收一個(gè) generator 對(duì)象,generator 函數(shù),thunk 函數(shù),Promise 對(duì)象,數(shù)組或者對(duì)象。co模塊的主要實(shí)現(xiàn)原理是將 yield 接收的值統(tǒng)一轉(zhuǎn)換成一個(gè)Promise對(duì)象,然后用類似上述 generator + Promise 的方法來(lái)自動(dòng)執(zhí)行 generator 函數(shù)。
下面是我根據(jù) node.js co 模塊源碼修改的 es6 co 模塊,讓它更適合自己使用:
https://github.com/lyl123321/...
yield接收thunk函數(shù):
import co from "./co.mjs" function* gen() { const data1 = yield getJSON_TH("https://www.apiopen.top/weatherApi?city=廣州"); const data2 = yield getJSON_TH("https://www.apiopen.top/weatherApi?city=番禺"); console.log(data1, data2); } co(gen);
yield接收Promise對(duì)象:
function* gen() { const data1 = yield getJSON_PM("https://www.apiopen.top/weatherApi?city=廣州"); const data2 = yield getJSON_PM("https://www.apiopen.top/weatherApi?city=番禺"); console.log(data1, data2); } co(gen);async/await
async函數(shù)是generator函數(shù)的語(yǔ)法糖,它相對(duì)于一個(gè)自帶執(zhí)行器(如 co 模塊)的generator函數(shù)。
async函數(shù)中的await關(guān)鍵字預(yù)期接收一個(gè)Promise對(duì)象,如果不是 Promise 對(duì)象則返回原值,這使得它的適用性比 co 執(zhí)行器更廣。
async函數(shù)返回一個(gè)Promise對(duì)象,這點(diǎn)與 co 執(zhí)行器一樣,這使得async函數(shù)比返回generator對(duì)象的generator函數(shù)更實(shí)用。如果 async 函數(shù)順利執(zhí)行完,則返回的 Promise 對(duì)象狀態(tài)變?yōu)?fulfilled,且 value 值為 async 函數(shù)中 return 關(guān)鍵字的返回值;如果 async 函數(shù)執(zhí)行時(shí)遇到錯(cuò)誤且沒(méi)有在 async 內(nèi)部捕獲錯(cuò)誤,則返回的 Promise 對(duì)象狀態(tài)變?yōu)?rejected,且 reason 值為 async 函數(shù)中的錯(cuò)誤。
await只處理Promise對(duì)象:
async function azc() { const data1 = await getJSON_PM("https://www.apiopen.top/weatherApi?city=廣州"); const data2 = await getJSON_PM("https://www.apiopen.top/weatherApi?city=番禺"); console.log(data1, data2); } azc();
async函數(shù)將generator函數(shù)的自動(dòng)執(zhí)行器,改在語(yǔ)言層面提供,不暴露給用戶。
async function fn(args) { // ... }
相當(dāng)于:
function fn(args) { return exec(function* () { // ... }); }
優(yōu)點(diǎn):最簡(jiǎn)潔,最符合語(yǔ)義,最接近同步代碼,最適合處理多個(gè) Promise 異步操作。相比 generator 方式,async 方式省掉了自動(dòng)執(zhí)行器,減少了代碼量。
缺點(diǎn):js 語(yǔ)言自帶的 async 執(zhí)行器功能性可能沒(méi)有 co 模塊等執(zhí)行器強(qiáng)。你可以根據(jù)自己的需求定義自己的 generator 函數(shù)執(zhí)行器。
參考鏈接:
http://es6.ruanyifeng.com/#do...
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/103044.html
摘要:雖然這個(gè)模式運(yùn)行效果很不錯(cuò),但是如果嵌套了太多的回調(diào)函數(shù),就會(huì)陷入回調(diào)地獄。當(dāng)需要跟蹤多個(gè)回調(diào)函數(shù)的時(shí)候,回調(diào)函數(shù)的局限性就體現(xiàn)出來(lái)了,非常好的改進(jìn)了這些情況。 JavaScript引擎是基于單線程 (Single-threaded) 事件循環(huán)的概念構(gòu)建的,同一時(shí)刻只允許一個(gè)代碼塊在執(zhí)行,所以需要跟蹤即將運(yùn)行的代碼,那些代碼被放在一個(gè)任務(wù)隊(duì)列 (job queue) 中,每當(dāng)一段代碼準(zhǔn)...
摘要:臆想的針對(duì)讀取到的內(nèi)容進(jìn)行操作,比如打印文件內(nèi)容臆想中,讀取文件是有返回值的,將返回值,即文件內(nèi)容,賦給一個(gè)變量,然后決定對(duì)讀取到的內(nèi)容進(jìn)行相應(yīng)的操作,例如打印文件中的內(nèi)容。 臆想的 let fs = require(fs) function readFile(filename){ ... } let content = readFile(config.js) // 針對(duì)讀...
摘要:本文給大家介紹的是相比于其他框架更靈活的配置方式,大家可以根據(jù)自己的項(xiàng)目需要選擇合適的方式。標(biāo)簽的方式下面我們看一個(gè)例子當(dāng)為時(shí)渲染我們可以看到這種路由配置方式使用標(biāo)簽,然后根據(jù)找到對(duì)應(yīng)的映射。 路由的概念 路由的作用就是將url和函數(shù)進(jìn)行映射,在單頁(yè)面應(yīng)用中路由是必不可少的部分,路由配置就是一組指令,用來(lái)告訴router如何匹配url,以及對(duì)應(yīng)的函數(shù)映射,即執(zhí)行對(duì)應(yīng)的代碼。 react...
摘要:事件中屬性等于。響應(yīng)的狀態(tài)為或者。同步在上會(huì)產(chǎn)生頁(yè)面假死的問(wèn)題。表示聲明的變量未初始化,轉(zhuǎn)換為數(shù)值時(shí)為。但并非所有瀏覽器都支持事件捕獲。它由兩部分構(gòu)成函數(shù),以及創(chuàng)建該函數(shù)的環(huán)境。 1 介紹JavaScript的基本數(shù)據(jù)類型Number、String 、Boolean 、Null、Undefined Object 是 JavaScript 中所有對(duì)象的父對(duì)象數(shù)據(jù)封裝類對(duì)象:Object、...
摘要:參與任何數(shù)值計(jì)算的結(jié)構(gòu)都是,而且。。面向人類的理性事物,而不是機(jī)器信號(hào)。達(dá)到無(wú)刷新效果。的工作原理總是指向一個(gè)對(duì)象,具體是運(yùn)行時(shí)基于函數(shù)的執(zhí)行環(huán)境動(dòng)態(tài)綁定的,而非函數(shù)被聲明時(shí)的環(huán)境。原型對(duì)象上有一個(gè)屬性,該屬性指向的就是構(gòu)造函數(shù)。 1.JS面向?qū)ο蟮睦斫?面向?qū)ο蟮娜筇攸c(diǎn):繼承、封裝、多態(tài) 1、JS中通過(guò)prototype實(shí)現(xiàn)原型繼承 2、JS對(duì)象可以通過(guò)對(duì)象冒充,實(shí)現(xiàn)多重繼承, 3...
閱讀 3207·2023-04-25 16:50
閱讀 941·2021-11-25 09:43
閱讀 3566·2021-09-26 10:11
閱讀 2547·2019-08-26 13:28
閱讀 2558·2019-08-26 13:23
閱讀 2457·2019-08-26 11:53
閱讀 3601·2019-08-23 18:19
閱讀 3019·2019-08-23 16:27