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

資訊專欄INFORMATION COLUMN

js 處理異步操作的幾種方式

Meils / 3018人閱讀

摘要:如果我們只有一個(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,generatorasync。

回調(diào)函數(shù)

我們首先用最傳統(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)聽

使用事件監(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ō)了。

Promise

使用 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ǔ)義變得不清楚。

generator + 回調(diào)函數(shù)

接下來(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ù)。

generator + Promise

除了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

相關(guān)文章

  • JS常用幾種異步流程控制

    摘要:雖然這個(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)...

    Barry_Ng 評(píng)論0 收藏0
  • 異步讀取文件幾種姿勢(shì)

    摘要:臆想的針對(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ì)讀...

    chinafgj 評(píng)論0 收藏0
  • 關(guān)于react-router幾種配置方式

    摘要:本文給大家介紹的是相比于其他框架更靈活的配置方式,大家可以根據(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...

    劉永祥 評(píng)論0 收藏0
  • 50道JavaScript基礎(chǔ)面試題(附答案)

    摘要:事件中屬性等于。響應(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、...

    huaixiaoz 評(píng)論0 收藏0
  • JavaScript知識(shí)點(diǎn)總結(jié)

    摘要:參與任何數(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...

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

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

0條評(píng)論

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