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

資訊專欄INFORMATION COLUMN

JavaScript工作原理(四):事件循環(huán),異步編程的興起以及5招async/await實(shí)踐

piglei / 2208人閱讀

摘要:事件循環(huán)從回調(diào)隊(duì)列中獲取并將其推送到調(diào)用堆棧。如何工作請(qǐng)注意,不會(huì)自動(dòng)將您的回調(diào)函數(shù)放到事件循環(huán)隊(duì)列中。它設(shè)置了一個(gè)計(jì)時(shí)器,當(dāng)計(jì)時(shí)器到期時(shí),環(huán)境將您的回調(diào)函數(shù)放入事件循環(huán)中,以便將來的某個(gè)事件會(huì)將其選中并執(zhí)行它。

我們將通過回顧第一篇文章中單線程編程的缺點(diǎn),然后在討論如何克服它們來構(gòu)建令人驚嘆的JavaScript UI。在文章結(jié)尾處,我們將分享5個(gè)關(guān)于如何使用async / await編寫更簡潔的代碼的技巧。

單線程的局限性

第一篇文章中,我們分析了如果在Call Stack中調(diào)用耗時(shí)長的函數(shù),會(huì)產(chǎn)生很多問題。

想象一下,一個(gè)復(fù)雜圖像轉(zhuǎn)換算法在瀏覽器中運(yùn)行。

當(dāng)Call Stack有函數(shù)需要執(zhí)行的時(shí)候,瀏覽器是無法執(zhí)行其他任何操作的 - 沒錯(cuò)它被阻塞了。這意味著瀏覽器無法渲染頁面,也不能運(yùn)行任何其他代碼,它只是卡住了。問題來了 - 您的應(yīng)用用戶界面不再高效和令人滿意。

在某些情況下,這可能不是至關(guān)重要的問題。但是,它可能引起一個(gè)更大的問題。一旦您的瀏覽器開始處理Call Stack中的太多任務(wù),它可能會(huì)停止響應(yīng)很長時(shí)間。很多瀏覽器會(huì)彈出錯(cuò)誤處理窗口,詢問他們是否應(yīng)該終止該頁面,這很丑陋,它完全毀了你的用戶體驗(yàn):

JavaScript程序的構(gòu)建塊

您可能會(huì)把所有JavaScript代碼寫入一個(gè).js文件,但是你的代碼幾乎肯定由幾個(gè)塊組成,其中只有一個(gè)將立即執(zhí)行,其余的將在稍后執(zhí)行。最常見的塊單位是函數(shù)。

大多數(shù)新的JavaScript的開發(fā)者似乎都有這樣的理解,即以后不一定會(huì)嚴(yán)格地立即發(fā)生。換句話說,根據(jù)定義,現(xiàn)在無法完成的任務(wù)將異步完成,這意味著您不會(huì)出現(xiàn)上述阻止行為,因?yàn)槟赡芤呀?jīng)潛意識(shí)地預(yù)期或期望。

我們來看看下面的例子:

// ajax(..) 是由其它工具庫提供的函數(shù)
var response = ajax("https://example.com/api");

console.log(response);
// `response` 不會(huì)有結(jié)果

您可能知道標(biāo)準(zhǔn)的Ajax請(qǐng)求是不會(huì)同步完成的,這意味著在代碼執(zhí)行時(shí),ajax(..)函數(shù)還沒有任何值返回以分配給response變量。

一個(gè)簡單實(shí)現(xiàn)“等待”異步函數(shù)返回結(jié)果的方法就是callback的函數(shù):

ajax("https://example.com/api", function(response) {
    console.log(response); // `response` 有值了
});

請(qǐng)注意:您實(shí)際上可以創(chuàng)建同步的Ajax請(qǐng)求。但是永遠(yuǎn)不要這樣做,如果您發(fā)出同步Ajax請(qǐng)求,您的JavaScript應(yīng)用的用戶界面將被阻塞 - 用戶將無法點(diǎn)擊,或輸入數(shù)據(jù),導(dǎo)航或滾動(dòng)。這將阻止任何用戶交互。沒錯(cuò)這是一個(gè)可怕的做法。

同步ajax請(qǐng)求代碼如下,但請(qǐng)不要這樣做:

// 假設(shè)你是用jquery庫
jQuery.ajax({
    url: "https://api.example.com/endpoint",
    success: function(response) {
        // 你的回調(diào)函數(shù)
    },
    async: false // 壞主意
});

Ajax請(qǐng)求只是其中一個(gè)例子。你可以讓任何代碼塊異步執(zhí)行。

這個(gè)可以通過setTimeout(回調(diào),毫秒)函數(shù)來完成。setTimeout函數(shù)的作用是設(shè)置一個(gè)事件(超時(shí))過一段時(shí)間再執(zhí)行。 讓我們來看看:

function first() {
    console.log("first");
}
function second() {
    console.log("second");
}
function third() {
    console.log("third");
}
first();
setTimeout(second, 1000); // Invoke `second` after 1000ms
third();

輸出如下:

first
third
second
解析事件循環(huán)

盡管允許異步JavaScript代碼(如我們剛才討論的setTimeout),但直到ES6,JavaScript本身實(shí)際上從來沒有內(nèi)置任何異步的直接概念,但我們將從一個(gè)有點(diǎn)奇怪的說法開始。 JavaScript引擎從來沒有做過比在任何特定時(shí)刻執(zhí)行單個(gè)程序塊更多的事情。

有關(guān)JavaScript引擎如何工作的(特別是Google的V8),請(qǐng)查看本系列第三篇文章。

那么,誰告訴JS引擎來執(zhí)行你的程序塊?實(shí)際上,JS引擎并不是孤立運(yùn)行的 - 它運(yùn)行在一個(gè)托管環(huán)境中,對(duì)于大多數(shù)開發(fā)人員來說,它是Web瀏覽器或Node.js。事實(shí)上,現(xiàn)在,JavaScript被嵌入到各種設(shè)備中,從機(jī)器人到燈泡。每個(gè)設(shè)備都代表JS Engine的不同類型的托管環(huán)境。

所有環(huán)境中的共同點(diǎn)是一種稱為事件循環(huán)的內(nèi)置機(jī)制,它隨著時(shí)間的推移處理程序中多個(gè)代碼塊的執(zhí)行,每次調(diào)用JS引擎。

這意味著JS引擎只是JS代碼的按需執(zhí)行環(huán)境。它是調(diào)度事件(JS代碼執(zhí)行)的周圍環(huán)境。

例如,當(dāng)您的JavaScript程序發(fā)出Ajax請(qǐng)求,想要從服務(wù)器獲取一些數(shù)據(jù)時(shí),您可以在函數(shù)中設(shè)置“響應(yīng)”代碼(“回調(diào)”),并且JS引擎會(huì)告訴主機(jī)環(huán)境:
“嘿,我現(xiàn)在暫停執(zhí)行,但每當(dāng)你完成這個(gè)網(wǎng)絡(luò)請(qǐng)求,并且你有一些數(shù)據(jù),請(qǐng)執(zhí)行這個(gè)函數(shù)?!?/p>

然后設(shè)置瀏覽器來偵聽來自網(wǎng)絡(luò)的響應(yīng),當(dāng)它返回給您時(shí),它將通過將回調(diào)函數(shù)插入到事件循環(huán)中來安排執(zhí)行回調(diào)函數(shù)。

我們來看下面的圖表:

您可以在本系列第一篇文章中閱讀關(guān)于內(nèi)存堆和調(diào)用堆棧的更多信息。

這些Web API是什么?從本質(zhì)上講,它們是你無法訪問的線程,你可以對(duì)它們進(jìn)行調(diào)用。它們是瀏覽器并發(fā)功能的一部分。如果您是Node.js開發(fā)人員,那么這些是C++ API。

那么究竟是什么事件循環(huán)呢?

事件循環(huán)只有一個(gè)簡單的工作 - 監(jiān)視Call Stack(調(diào)用堆棧)和Callback Queue(回調(diào)隊(duì)列)。如果調(diào)用堆棧為空,它將從回調(diào)隊(duì)列中取出第一個(gè)事件并將其推送到調(diào)用堆棧,該調(diào)用堆??梢杂行У剡\(yùn)行它。

這種迭代在事件循環(huán)中稱為tick。每個(gè)事件只是一個(gè)函數(shù)回調(diào)。

console.log("Hi");
setTimeout(function cb1() { 
    console.log("cb1");
}, 5000);
console.log("Bye");

讓我們“執(zhí)行”這段代碼,看看會(huì)發(fā)生什么:

狀態(tài)很清楚。瀏覽器控制臺(tái)已清除,并且調(diào)用堆棧為空

console.log("Hi")被添加到Call Stack

執(zhí)行console.log("Hi")

從Call Stack中移除console.log("Hi")

setTimeout(function cb1() { ... })被添加到Call Stack

執(zhí)行setTimeout(function cb1() { ... }),瀏覽器將創(chuàng)建一個(gè)計(jì)時(shí)器作為Web API的一部分。 它將為您處理倒計(jì)時(shí)

setTimeout(function cb1() { ... })自身執(zhí)行結(jié)束,然后從Call Stack中移除

console.log("Bye")被添加到Call Stack

執(zhí)行console.log("Bye")

從Call Stack中移除執(zhí)行console.log("Bye")

至少5000毫秒后,定時(shí)器完成并將cb1回調(diào)函數(shù)推送到Callback隊(duì)列中。

事件循環(huán)從回調(diào)隊(duì)列中獲取cb1并將其推送到調(diào)用堆棧。

cb1執(zhí)行,添加console.log("cb1")到調(diào)用堆棧

console.log("cb1")執(zhí)行

console.log("cb1")從調(diào)用堆棧中移除

cb1從調(diào)用堆棧中移除

扼要重述:

有趣的是,ES6指定了事件循環(huán)應(yīng)該如何工作,這意味著它在JS引擎的職責(zé)范圍內(nèi),而不再只是屬于一個(gè)托管環(huán)境。這種變化的一個(gè)主要原因是在ES6中引入了Promises,因?yàn)楹笳咝枰獙?duì)事件循環(huán)隊(duì)列上的調(diào)度操作進(jìn)行直接,細(xì)粒度的控制(我們稍后會(huì)更詳細(xì)地討論它們)。

setTimeout(…)如何工作

請(qǐng)注意,setTimeout(...)不會(huì)自動(dòng)將您的回調(diào)函數(shù)放到事件循環(huán)隊(duì)列中。它設(shè)置了一個(gè)計(jì)時(shí)器,當(dāng)計(jì)時(shí)器到期時(shí),環(huán)境將您的回調(diào)函數(shù)放入事件循環(huán)中,以便將來的某個(gè)tick事件會(huì)將其選中并執(zhí)行它。查看此代碼:

setTimeout(myCallback, 1000);

這并不意味著myCallback將在1000ms之后馬上執(zhí)行,而是在1000ms之后,myCallback將被添加到隊(duì)列中。但是隊(duì)列中可能還有其他事件先前已添加 - 您的回調(diào)將不得不等待。

有很多關(guān)于開始使用JavaScript中的異步代碼的文章和教程,其中提到了setTimeout(callback,0)。 那么,現(xiàn)在你知道Event Loop的作用了,以及setTimeout如何工作:使用0作為第二個(gè)參數(shù)調(diào)用setTimeout只是推遲回調(diào)函數(shù)執(zhí)行,直到調(diào)用堆棧清空才執(zhí)行。

看看下面的代碼:

console.log("Hi");
setTimeout(function() {
    console.log("callback");
}, 0);
console.log("Bye");

雖然等待時(shí)間設(shè)置為0 ms,但瀏覽器控制臺(tái)中的結(jié)果如下所示:

Hi
Bye
callback
ES6中的Jobs?

ES6中引入了一個(gè)名為“Job隊(duì)列”的新概念。它是Event Loop隊(duì)列頂部的一個(gè)層。在處理Promises的異步行為時(shí),您最有可能接觸到它(我們也將討論它們)。

現(xiàn)在我們將簡單介紹這個(gè)概念,以便在我們稍后討論P(yáng)romise的異步行為時(shí),您將了解如何安排和處理這些操作。

想象一下:Job隊(duì)列是一個(gè)連接到事件循環(huán)隊(duì)列中每個(gè)tick的末尾的隊(duì)列。在事件循環(huán)的tick期間可能發(fā)生的某些異步操作不會(huì)導(dǎo)致將全新的事件添加到事件循環(huán)隊(duì)列中,而是會(huì)將一個(gè)項(xiàng)(又名Job)添加到當(dāng)前tick的Job隊(duì)列的末尾。

這意味著您可以添加其他功能以便稍后執(zhí)行,您可以放心,它將在執(zhí)行任何其他操作之前立即執(zhí)行。

Job還可以使更多作業(yè)添加到同一隊(duì)列的末尾。從理論上講,作業(yè)“循環(huán)”(一個(gè)不停地添加其他作業(yè)等的作業(yè))可能會(huì)無限地旋轉(zhuǎn),從而導(dǎo)致需要進(jìn)入下一個(gè)事件循環(huán)節(jié)點(diǎn)所需的必要資源的程序不足。從概念上講,這與在代碼中僅表示長時(shí)間運(yùn)行或無限循環(huán)(如while(true)..)類似。

作業(yè)有點(diǎn)像setTimeout(回調(diào),0)“破解”,但實(shí)現(xiàn)的方式是它們引入了一個(gè)更加明確和有保證的排序:稍后,但盡快。

回調(diào)

如您所知,回調(diào)是迄今為止在JavaScript程序中表達(dá)和管理異步的最常見方式。事實(shí)上,回調(diào)是JavaScript語言中最基本的異步模式。無數(shù)的JS程序,甚至是非常復(fù)雜和復(fù)雜的程序,都是在沒有其他異步基礎(chǔ)的情況下編寫的,而不是回調(diào)。

除了回調(diào)不具有缺點(diǎn)。許多開發(fā)人員正試圖找到更好的異步模式。然而,如果你不了解底層實(shí)際情況,那么有效地使用任何抽象概念是不可能的。

在下一章中,我們將深入探索這些抽象概念,以說明為什么更復(fù)雜的異步模式是必要的甚至是推薦的(將在后續(xù)的帖子中討論)。

嵌套的回調(diào)

看下面的代碼:

listen("click", function (e){
    setTimeout(function(){
        ajax("https://api.example.com/endpoint", function (text){
            if (text == "hello") {
                doSomething();
            }
            else if (text == "world") {
                doSomethingElse();
            }
       });
    }, 500);
});

我們有一個(gè)嵌套在一起的三個(gè)函數(shù),每個(gè)函數(shù)代表一個(gè)異步過程。

這種代碼通常被稱為“回調(diào)地獄”。但“回?fù)艿鬲z”實(shí)際上與嵌套/縮進(jìn)幾乎沒有任何關(guān)系。這是一個(gè)比這更深的問題。

首先,我們正在等待“click”事件,然后等待計(jì)時(shí)器開始工作,然后等待Ajax響應(yīng)返回,此時(shí)它可能會(huì)再次重復(fù)。

乍一看,這段代碼看起來可以將其異步映射為連續(xù)的步驟:

listen("click", function (e) {
    // ..
});

之后:

setTimeout(function(){
    // ..
}, 500);

最后:

if (text == "hello") {
    doSomething();
}
else if (text == "world") {
    doSomethingElse();
}

因此,表達(dá)異步代碼的這種順序方式似乎更加自然,不是嗎? 一定有這樣的方式吧?

Promises

看看下面的代碼:

var x = 1;
var y = 2;
console.log(x + y);

它非常簡單:它將x和y的值相加并打印到控制臺(tái)。但是,如果x或y的值需要異步返回,該怎么辦?比方說,我們需要從服務(wù)器中檢索x和y的值,然后才能在表達(dá)式中使用它們。假設(shè)我們有一個(gè)函數(shù)loadX和loadY,分別從服務(wù)器加載x和y的值。然后,想象一下,我們有一個(gè)函數(shù)sum,返回x加y的值。

它可能看起來像這樣(相當(dāng)丑陋):

function sum(getX, getY, callback) {
    var x, y;
    getX(function(result) {
        x = result;
        if (y !== undefined) {
            callback(x + y);
        }
    });
    getY(function(result) {
        y = result;
        if (x !== undefined) {
            callback(x + y);
        }
    });
}
// 一個(gè)同步或異步函數(shù)返回x的值
function fetchX() {
    // ..
}


// 一個(gè)同步或異步函數(shù)返回y的值
function fetchY() {
    // ..
}
sum(fetchX, fetchY, function(result) {
    console.log(result);
});

這里有一些非常重要的東西 - 在這個(gè)代碼中,我們將x和y作為未來值,并且我們表達(dá)了一個(gè)操作和(...)(從外部)不關(guān)心x或y或者兩者是否都不可用 馬上。

當(dāng)然,這種基于簡單回調(diào)的方法還有很多不足之處。這只是為了解feature values的好處的第一步,而不必?fù)?dān)心它們何時(shí)可用。

Promises的值

讓我們簡要地看一下我們?nèi)绾斡肞romises來表達(dá)x + y示例:

function sum(xPromise, yPromise) {
    // `Promise.all([ .. ])` 接受 promises 數(shù)組,
    // 返回一個(gè)新的promise,這個(gè)promise會(huì)等待所有promise數(shù)組完成
    return Promise.all([xPromise, yPromise])

    // Promise.all被resolved之后, 我們將返回的X和Y相加
    .then(function(values){
        // `values` 是之前promises數(shù)組中每個(gè)promise解決之后的信息組成的數(shù)組
        return values[0] + values[1];
    } );
}

// `fetchX()` and `fetchY()` 返回promise,promise包含各自的值
// 這個(gè)值可能可用也可能不可用
sum(fetchX(), fetchY())

// 我們最終得到一個(gè)promise,它返回了兩個(gè)數(shù)字的和
// 調(diào)用 `then(...)` 得到最終值
.then(function(sum){
    console.log(sum);
});

在這個(gè)片段中有兩層Promise。

直接調(diào)用fetchX()和fetchY(),并將它們返回的值(promise?。﹤鬟f給sum(...)。這些承諾所代表的基礎(chǔ)價(jià)值可能現(xiàn)在已經(jīng)準(zhǔn)備就緒,但是每個(gè)承諾都將其行為規(guī)范化為無論如何都是相同的。我們以時(shí)間無關(guān)的方式推測(cè)x和y值。他們是未來的價(jià)值觀,期限。

第二層是sum(...)創(chuàng)建的承諾
(通過Promise.all([...]))和返回,我們通過調(diào)用然后等待(...)??偤停?..)操作完成后,我們的總和未來值已準(zhǔn)備好,我們可以將其打印出來。我們隱藏了等待sum(...)中x和y未來值的邏輯。

注意:Inside sum(...)中,Promise.all([...])調(diào)用創(chuàng)建一個(gè)承諾(等待promiseX并promiseY解析)。然后(...)的鏈接調(diào)用創(chuàng)建了另一個(gè)承諾,即返回
值[0] +值[1]行立即解決(與加法的結(jié)果)。因此,我們連接sum(...)調(diào)用結(jié)束時(shí)的then(...)調(diào)用 - 在片段結(jié)尾處 - 實(shí)際上是在返回的第二個(gè)promise上運(yùn)行,而不是由Promise創(chuàng)建的第一個(gè)promise。全部([...])。另外,雖然我們并沒有把時(shí)間的尾端連接起來(...),但是如果我們選擇觀察/使用它,它也創(chuàng)造了另一個(gè)承諾。本章后面將詳細(xì)解釋這個(gè)Promise鏈接的東西。

有了Promises,那么(...)調(diào)用實(shí)際上可以采用兩個(gè)函數(shù),第一個(gè)用于履行(如前所示),第二個(gè)用于拒絕:

sum(fetchX(), fetchY())
.then(
    // fullfillment handler
    function(sum) {
        console.log( sum );
    },
    // rejection handler
    function(err) {
        console.error( err ); // bummer!
    }
);

如果在獲取x或y時(shí)出現(xiàn)問題,或者在添加期間某種方式失敗了,那么sum(...)返回的promise將被拒絕,并且傳遞給then(...)的第二個(gè)回調(diào)錯(cuò)誤處理程序?qū)⑹盏骄芙^ 來自諾言的價(jià)值。

由于Promises封裝了時(shí)間依賴狀態(tài) - 等待基礎(chǔ)價(jià)值的實(shí)現(xiàn)或拒絕 - 從外部看,Promise本身是時(shí)間無關(guān)的,因此Promises可以以可預(yù)測(cè)的方式組合(組合),而不管時(shí)間或結(jié)果如何 下。

而且,一旦一個(gè)承諾解決了,它就會(huì)永遠(yuǎn)保持這種狀態(tài) - 它在那個(gè)時(shí)候成為一個(gè)不變的價(jià)值 - 然后可以根據(jù)需要多次觀察。

確實(shí)可以鏈接承諾是非常有用的:

function delay(time) {
    return new Promise(function(resolve, reject){
        setTimeout(resolve, time);
    });
}

delay(1000)
.then(function(){
    console.log("after 1000ms");
    return delay(2000);
})
.then(function(){
    console.log("after another 2000ms");
})
.then(function(){
    console.log("step 4 (next Job)");
    return delay(5000);
})
// ...

呼叫延遲(2000)創(chuàng)建了一個(gè)將在2000ms完成的承諾,然后我們從第一個(gè)(...)履行回調(diào)中返回,這導(dǎo)致第二個(gè)(...)的承諾等待2000ms的承諾。

注意:因?yàn)镻romise一旦解決就是外部不可變的,現(xiàn)在可以安全地將該值傳遞給任何一方,并知道它不能被意外或惡意修改。 關(guān)于觀察解決諾言的多方,這一點(diǎn)尤其如此。 一方不可能影響另一方遵守Promise解決方案的能力。 不變性可能聽起來像是一個(gè)學(xué)術(shù)話題,但它實(shí)際上是Promise設(shè)計(jì)的最基本和最重要的方面之一,不應(yīng)該隨便傳遞。

使用還是不使用Promise

關(guān)于Promises的一個(gè)重要細(xì)節(jié)是確切地知道某個(gè)值是否是實(shí)際的Promises。 換句話說,這是一種會(huì)表現(xiàn)得像一個(gè)Promise?

我們知道Promise是由new Promise(...)語法構(gòu)造的,您可能認(rèn)為Promise的instanceof將是一個(gè)有效的檢查。好吧,不是。

主要是因?yàn)槟梢詮牧硪粋€(gè)瀏覽器窗口(例如iframe)接收Promise值,該窗口具有與當(dāng)前窗口或框架中的承諾不同的Promise,并且該檢查無法識(shí)別Promise實(shí)例。

此外,庫或框架可能會(huì)選擇出售自己的Promises,而不是使用原生ES6的Promise實(shí)施來實(shí)現(xiàn)。 事實(shí)上,你可能會(huì)在早期的瀏覽器中使用Promises和Promise來實(shí)現(xiàn)Promise。

異常

如果在創(chuàng)建Promise或觀察其解決方案的任何時(shí)候發(fā)生JavaScript異常錯(cuò)誤(例如TypeError或ReferenceError),該異常將被捕獲,并且它將強(qiáng)制有問題的Promise被拒絕。

例如:

var p = new Promise(function(resolve, reject){
    foo.bar();      // `foo` 沒有被定義, 所以會(huì)發(fā)出異?;蝈e(cuò)誤
    resolve(374); // 不會(huì)運(yùn)行到這里 :(
});

p.then(
    function fulfilled(){
        // 不會(huì)運(yùn)行到這里 :(
    },
    function rejected(err){
        // `err` 是一個(gè) `TypeError` 異常對(duì)象
    // 異常發(fā)生在 `foo.bar()` 這一行.
    }
);

但是如果一個(gè)Promise被實(shí)現(xiàn)時(shí),在observation期間(在一個(gè)then(...)注冊(cè)的回調(diào)中)有一個(gè)JS異常錯(cuò)誤會(huì)發(fā)生什么? 即使它不會(huì)丟失,你可能會(huì)發(fā)現(xiàn)它們的處理方式有點(diǎn)令人驚訝。直到你深入一點(diǎn):

var p = new Promise( function(resolve,reject){
    resolve(374);
});

p.then(function fulfilled(message){
    foo.bar();
    console.log(message);   // 沒有運(yùn)行到這里
},
    function rejected(err){
        // 沒有運(yùn)行到這里
    }
);

它看起來像來自foo.bar()的異常真的被吞噬了。不過事實(shí)上并非如此。然而,有些更深層的事情發(fā)生了錯(cuò)誤,但我們沒有監(jiān)聽到。p.then(...)調(diào)用本身會(huì)返回另一個(gè)promise,這就是那個(gè)將被TypeError異常拒絕的promise

處理未捕獲的異常

還有其他的方法,很多人會(huì)說更好。

一個(gè)常見的建議是Promise應(yīng)該使用done(...),它們基本上將Promise鏈標(biāo)記為“已完成”。done(...)不會(huì)創(chuàng)建并返回Promise,所以回調(diào)函數(shù)傳遞給done(..)顯然沒有連接到向不存在的鏈?zhǔn)匠兄Z報(bào)告問題。

它的處理方式與您在未捕獲的錯(cuò)誤情況中通常所期待的一樣:done(..)里面的異?;蝈e(cuò)誤,將作為全局未捕獲錯(cuò)誤引發(fā)(基本上在開發(fā)人員控制臺(tái)中):

var p = Promise.resolve(374);

p.then(function fulfilled(msg){
    // 數(shù)字是不會(huì)有字符串的處理函數(shù)
    // 所以會(huì)拋出異常
    console.log(msg.toLowerCase());
})
.done(null, function() {
    // 如果異常在這里發(fā)生,它會(huì)全局拋出
});
ES8(ES2017)async/await

JavaScript ES8(ES2017)引入了async/await,這使得使用Promises的工作更容易。我們將簡要介紹async/await提供的可能性以及如何利用它們來編寫異步代碼。

那么,讓我們看看async/await如何工作。

您可以使用async關(guān)鍵字聲明定義一個(gè)異步函數(shù)。這樣的函數(shù)返回一個(gè)AsyncFunction對(duì)象。 AsyncFunction對(duì)象表示執(zhí)行該函數(shù)中包含的代碼的異步函數(shù)。

當(dāng)一個(gè)異步函數(shù)被調(diào)用時(shí),它返回一個(gè)Promise。當(dāng)異步函數(shù)返回一個(gè)值時(shí),這不是一個(gè)Promise,Promise將會(huì)自動(dòng)創(chuàng)建,并且會(huì)使用函數(shù)返回的值來解析。當(dāng)異步函數(shù)拋出異常時(shí),Promise將被拋出的值拒絕。

異步函數(shù)可以包含await表達(dá)式,暫停執(zhí)行該函數(shù)并等待傳遞的Promise的解析,然后恢復(fù)異步函數(shù)的執(zhí)行并返回解析后的值。

您可以將JavaScript中的Promise等同于Java的Future或C#的Task。

async/await的目的是為了簡化使用promises。
我們來看看下面的例子:

// 標(biāo)準(zhǔn)的javascript函數(shù)
function getNumber1() {
    return Promise.resolve("374");
}
// 功能和getNumber相同
async function getNumber2() {
    return 374;
}

同樣,拋出異常的函數(shù)等價(jià)于返回已被reject的promise的函數(shù):

function f1() {
    return Promise.reject("Some error");
}
async function f2() {
    throw "Some error";
}

await關(guān)鍵字只能用于異步功能,并允許您同步等待Promise。 如果我們?cè)诋惒胶瘮?shù)之外使用promise,我們?nèi)匀槐仨毷褂没卣{(diào)函數(shù):

async function loadData() {
    // `rp` is a request-promise function.
    var promise1 = rp("https://api.example.com/endpoint1");
    var promise2 = rp("https://api.example.com/endpoint2");
   
    // Currently, both requests are fired, concurrently and
    // now we"ll have to wait for them to finish
    var response1 = await promise1;
    var response2 = await promise2;
    return response1 + " " + response2;
}
// Since, we"re not in an `async function` anymore
// we have to use `then`.
loadData().then(() => console.log("Done"));

還可以使用“異步函數(shù)表達(dá)式”來定義異步函數(shù)。 異步函數(shù)表達(dá)式與異步函數(shù)語句非常相似,語法幾乎相同。異步函數(shù)表達(dá)式和異步函數(shù)語句之間的主要區(qū)別在于函數(shù)名稱,在異步函數(shù)表達(dá)式中可以省略這些名稱以創(chuàng)建匿名函數(shù)。異步函數(shù)表達(dá)式可以用作IIFE(立即調(diào)用的函數(shù)表達(dá)式),只要定義它就立即運(yùn)行。

它看起來像這樣:

var loadData = async function() {
    // `rp` is a request-promise function.
    var promise1 = rp("https://api.example.com/endpoint1");
    var promise2 = rp("https://api.example.com/endpoint2");
   
    // Currently, both requests are fired, concurrently and
    // now we"ll have to wait for them to finish
    var response1 = await promise1;
    var response2 = await promise2;
    return response1 + " " + response2;
}

更重要的是,所有主流瀏覽器都支持async/await:

工作一天結(jié)束時(shí),重要的是不要盲目選擇“最新”方法編寫異步代碼。理解異步JavaScript的內(nèi)部特性至關(guān)重要,并深入了解所選方法的內(nèi)部原理。與編程中的其他所有方法一樣,每種方法都有優(yōu)點(diǎn)和缺點(diǎn)。

編寫高度可維護(hù),強(qiáng)壯的異步代碼的5個(gè)技巧

代碼整潔:使用async/await可以減少你的代碼體積,因?yàn)樗梢月赃^一些不必要的步驟:.then鏈,處理結(jié)果的匿名函數(shù)和回調(diào)函數(shù)中定義結(jié)果變量

// `rp` is a request-promise function.
rp(‘https://api.example.com/endpoint1").then(function(data) {
 // …
});

使用async/await之后:

// `rp` is a request-promise function.
var response = await rp(‘https://api.example.com/endpoint1");

錯(cuò)誤處理:

async/await使相同的代碼結(jié)構(gòu)來處理同步或異步的錯(cuò)誤(或異常)稱為可能,比如熟悉的try/catch語句,下面的例子使用Promises:

function loadData() {
    try { // 捕獲同步錯(cuò)誤
        getJSON().then(function(response) {
            var parsed = JSON.parse(response);
            console.log(parsed);
        }).catch(function(e) { // 捕獲異步錯(cuò)誤
            console.log(e); 
        });
    } catch(e) {
        console.log(e);
    }
}

使用async/await之后:

async function loadData() {
    try {
        var data = JSON.parse(await getJSON());
        console.log(data);
    } catch(e) {
        console.log(e);
    }
}

使用條件:使用條件式代碼結(jié)合async/await更加簡單

function loadData() {
  return getJSON()
    .then(function(response) {
      if (response.needsAnotherRequest) {
        return makeAnotherRequest(response)
          .then(function(anotherResponse) {
            console.log(anotherResponse)
            return anotherResponse
          })
      } else {
        console.log(response)
        return response
      }
    })
}

使用async/await之后:

async function loadData() {
  var response = await getJSON();
  if (response.needsAnotherRequest) {
    var anotherResponse = await makeAnotherRequest(response);
    console.log(anotherResponse)
    return anotherResponse
  } else {
    console.log(response);
    return response;    
  }
}

堆棧幀:

使用promise鏈,很難定位錯(cuò)誤發(fā)生的位置:

function loadData() {
  return callAPromise()
    .then(callback1)
    .then(callback2)
    .then(callback3)
    .then(() => {
      throw new Error("boom");
    })
}
loadData()
  .catch(function(e) {
    console.log(err);
// Error: boom at callAPromise.then.then.then.then (index.js:8:13)
});

使用async/await之后:

async function loadData() {
  await callAPromise1()
  await callAPromise2()
  await callAPromise3()
  await callAPromise4()
  await callAPromise5()
  throw new Error("boom");
}
loadData()
  .catch(function(e) {
    console.log(err);
    // output
    // Error: boom at loadData (index.js:7:9)
});

調(diào)試:如果你使用過promises,你知道調(diào)試它們是一場噩夢(mèng)。例如,如果您在.then塊內(nèi)設(shè)置斷點(diǎn)并使用調(diào)試快捷方式(如“step over”),則調(diào)試器將不會(huì)移動(dòng)到以下位置,因?yàn)樗鼉H通過同步代碼“執(zhí)行”。

通過異步/等待,您可以完全按照正常的同步功能一步一步地調(diào)試。

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

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

相關(guān)文章

  • JavaScript 工作原理事件循環(huán)異步編程出現(xiàn)和 5 種更好 async/await

    摘要:函數(shù)會(huì)在之后的某個(gè)時(shí)刻觸發(fā)事件定時(shí)器。事件循環(huán)中的這樣一次遍歷被稱為一個(gè)。執(zhí)行完畢并出棧。當(dāng)定時(shí)器過期,宿主環(huán)境會(huì)把回調(diào)函數(shù)添加至事件循環(huán)隊(duì)列中,然后,在未來的某個(gè)取出并執(zhí)行該事件。 原文請(qǐng)查閱這里,略有改動(dòng)。 本系列持續(xù)更新中,Github 地址請(qǐng)查閱這里。 這是 JavaScript 工作原理的第四章。 現(xiàn)在,我們將會(huì)通過回顧單線程環(huán)境下編程的弊端及如何克服這些困難以創(chuàng)建令人驚嘆...

    maochunguang 評(píng)論0 收藏0
  • JavaScript 異步

    摘要:從最開始的到封裝后的都在試圖解決異步編程過程中的問題。為了讓編程更美好,我們就需要引入來降低異步編程的復(fù)雜性。寫一個(gè)符合規(guī)范并可配合使用的寫一個(gè)符合規(guī)范并可配合使用的理解的工作原理采用回調(diào)函數(shù)來處理異步編程。 JavaScript怎么使用循環(huán)代替(異步)遞歸 問題描述 在開發(fā)過程中,遇到一個(gè)需求:在系統(tǒng)初始化時(shí)通過http獲取一個(gè)第三方服務(wù)器端的列表,第三方服務(wù)器提供了一個(gè)接口,可通過...

    tuniutech 評(píng)論0 收藏0
  • ES6-7

    摘要:的翻譯文檔由的維護(hù)很多人說,阮老師已經(jīng)有一本關(guān)于的書了入門,覺得看看這本書就足夠了。前端的異步解決方案之和異步編程模式在前端開發(fā)過程中,顯得越來越重要。為了讓編程更美好,我們就需要引入來降低異步編程的復(fù)雜性。 JavaScript Promise 迷你書(中文版) 超詳細(xì)介紹promise的gitbook,看完再不會(huì)promise...... 本書的目的是以目前還在制定中的ECMASc...

    mudiyouyou 評(píng)論0 收藏0
  • 夯實(shí)基礎(chǔ)-JavaScript異步編程

    摘要:調(diào)用棧被清空,消息隊(duì)列中并無任務(wù),線程停止,事件循環(huán)結(jié)束。不確定的時(shí)間點(diǎn)請(qǐng)求返回,將設(shè)定好的回調(diào)函數(shù)放入消息隊(duì)列。調(diào)用棧執(zhí)行完畢執(zhí)行消息隊(duì)列任務(wù)。請(qǐng)求并發(fā)回調(diào)函數(shù)執(zhí)行順序無法確定。 異步編程 JavaScript中異步編程問題可以說是基礎(chǔ)中的重點(diǎn),也是比較難理解的地方。首先要弄懂的是什么叫異步? 我們的代碼在執(zhí)行的時(shí)候是從上到下按順序執(zhí)行,一段代碼執(zhí)行了之后才會(huì)執(zhí)行下一段代碼,這種方式...

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

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

0條評(píng)論

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