[ES6] 異步處理

[回顧] 事件循環(huán)

JS運(yùn)行的環(huán)境稱之為宿主環(huán)境

執(zhí)行棧:call stack,一個(gè)數(shù)據(jù)結(jié)構(gòu),用于存放各種函數(shù)的執(zhí)行環(huán)境,每一個(gè)函數(shù)執(zhí)行之前,它的相關(guān)信息會(huì)加入到執(zhí)行棧。函數(shù)調(diào)用之前,創(chuàng)建執(zhí)行環(huán)境,然后加入到執(zhí)行棧;函數(shù)調(diào)用之后,銷毀執(zhí)行環(huán)境。

function a() {
    console.log("a");
    b()
}

function b(){
    console.log("b");
    c()
}

function c(){
    console.log("c")
}

console.log("全局")
a()

// 全局 a b c
// 首先在執(zhí)行棧插入全局上下文(入棧push),執(zhí)行l(wèi)og全局,執(zhí)行完就銷毀;
// 接著在執(zhí)行棧插入a的上下文(入棧push),然后執(zhí)行l(wèi)og(a),執(zhí)行完銷毀;
// 接著在執(zhí)行棧插入b的上下文(入棧push),然后執(zhí)行l(wèi)og(b),執(zhí)行完銷毀;
// 接著在執(zhí)行棧插入c的上下文(入棧push),然后執(zhí)行l(wèi)og(c),執(zhí)行完銷毀,函數(shù)C都執(zhí)行完后銷毀c的上下文(出棧pop);
// 銷毀b的上下文(出棧pop);
// 銷毀a的上下文(出棧pop);
// 最后銷毀全局上下文(出棧pop)

// 先進(jìn)后出,先入棧push永遠(yuǎn)在執(zhí)行棧的最頂端

JS引擎永遠(yuǎn)執(zhí)行的是執(zhí)行棧的最頂部。

異步函數(shù):某些函數(shù)不會(huì)立即執(zhí)行,需要等到某個(gè)時(shí)機(jī)到達(dá)后才會(huì)執(zhí)行,這樣的函數(shù)稱之為異步函數(shù)。比如事件處理函數(shù)。異步函數(shù)的執(zhí)行時(shí)機(jī),會(huì)被宿主環(huán)境控制。

console.log("a");

setTimeout(() => {
    console.log("b")
}, 0);

console.log("c")

// a c b

瀏覽器宿主環(huán)境中包含5個(gè)線程:

  1. JS引擎:負(fù)責(zé)執(zhí)行執(zhí)行棧的最頂部代碼
  2. GUI線程:負(fù)責(zé)渲染頁面
  3. 事件監(jiān)聽線程:負(fù)責(zé)監(jiān)聽各種事件
  4. 計(jì)時(shí)線程:負(fù)責(zé)計(jì)時(shí)
  5. 網(wǎng)絡(luò)線程:負(fù)責(zé)網(wǎng)絡(luò)通信

當(dāng)上面的線程發(fā)生了某些事情,如果該線程發(fā)現(xiàn),這件事情也有處理程序,它會(huì)將該處理程序加入一個(gè)叫做事件隊(duì)列的內(nèi)存。當(dāng)JS引擎發(fā)現(xiàn),執(zhí)行棧中已經(jīng)沒有了任何內(nèi)容后,會(huì)將事件隊(duì)列中的第一個(gè)函數(shù)加入到執(zhí)行棧中執(zhí)行。

JS引擎對事件隊(duì)列的取出執(zhí)行方式,以及與宿主環(huán)境的配合,稱之為事件循環(huán)。

事件隊(duì)列在不同的宿主環(huán)境中有所差異,大部分宿主環(huán)境會(huì)將事件隊(duì)列進(jìn)行細(xì)分。在瀏覽器中,事件隊(duì)列分為兩種:

  • 宏任務(wù)(隊(duì)列):macroTask,計(jì)時(shí)器結(jié)束的回調(diào)、事件回調(diào)、http回調(diào)等等絕大部分異步函數(shù)進(jìn)入宏隊(duì)列
  • 微任務(wù)(隊(duì)列):MutationObserver,Promise產(chǎn)生的回調(diào)進(jìn)入微隊(duì)列。

MutationObserver用于監(jiān)聽某個(gè)DOM對象的變化

當(dāng)執(zhí)行棧清空時(shí),JS引擎首先會(huì)將微任務(wù)中的所有任務(wù)依次執(zhí)行結(jié)束,如果沒有微任務(wù),則執(zhí)行宏任務(wù)。

事件和回調(diào)函數(shù)的缺陷

我們習(xí)慣于使用傳統(tǒng)的回調(diào)或事件處理來解決異步問題

事件:某個(gè)對象的屬性是一個(gè)函數(shù),當(dāng)發(fā)生某一件事時(shí),運(yùn)行該函數(shù)
回調(diào):運(yùn)行某個(gè)函數(shù)以實(shí)現(xiàn)某個(gè)功能的時(shí)候,傳入一個(gè)函數(shù)作為參數(shù),當(dāng)發(fā)生某件事的時(shí)候,會(huì)運(yùn)行該函數(shù)

本質(zhì)上,事件和回調(diào)并沒有本質(zhì)的區(qū)別,只是把函數(shù)放置的位置不同而已。

一直以來,該模式都運(yùn)作良好。

目前,該模式主要面臨以下兩個(gè)問題:

  1. 回調(diào)地獄:某個(gè)異步操作需要等待之前的異步操作完成,無論用回調(diào)還是事件,都會(huì)陷入不斷的嵌套

  2. 異步之間的聯(lián)系:某個(gè)異步操作要等待多個(gè)異步操作的結(jié)果,對這種聯(lián)系的處理,會(huì)讓代碼的復(fù)雜度劇增

異步處理的通用模型

ES官方參考了大量的異步場景,總結(jié)出了一套異步的通用模型,該模型可以覆蓋幾乎所有的異步場景,甚至是同步場景

值得注意的是,為了兼容舊系統(tǒng),ES6并不打算拋棄掉過去的做法,只是基于該模型推出一個(gè)全新的API,使用該API,會(huì)讓異步處理更加的簡潔優(yōu)雅。

理解該API,最重要的是理解它的異步模型

  1. ES6將某一件可能發(fā)生異步操作的事情,分為兩個(gè)階段:unsettledsettled
  • unsettled:未決階段,表示事情還在進(jìn)行前期的處理,并沒有發(fā)生通向結(jié)果的那件事

  • settled:已決階段,事情已經(jīng)有了一個(gè)結(jié)果,不管這個(gè)結(jié)果是好是壞,整件事情無法逆轉(zhuǎn)

事情總是從未決階段逐步發(fā)展到已決階段的。并且,未決階段擁有控制何時(shí)通向已決階段的能力。

  1. ES6將事情劃分為三種狀態(tài):pending, resolved, rejected
  • pending:掛起,處于未決階段,則表示這件事情還在掛起(最終結(jié)果還沒出來)
  • resolved:已處理,已決階段的一種狀態(tài),表示整件事情已經(jīng)出現(xiàn)結(jié)果,并是一個(gè)可以按照正常邏輯進(jìn)行下去的結(jié)果
  • rejected 已拒絕,已決階段的一種狀態(tài),表示整件事情已經(jīng)出現(xiàn)結(jié)果,并是一個(gè)無法按照正常邏輯進(jìn)行下去的結(jié)果,通常用于表示有一個(gè)錯(cuò)誤

既然未決階段有權(quán)利決定事情走向,因此,未決階段可以決定事情最終的狀態(tài)

我們將把事情變?yōu)?code>resolved狀態(tài)的過程叫做:resolve,推向該狀態(tài)時(shí),可能會(huì)傳遞一些數(shù)據(jù)

我們將把事情變?yōu)?code>rejected狀態(tài)的過程叫做:reject,推向該狀態(tài)時(shí),同樣可能會(huì)傳遞一些數(shù)據(jù),通常為錯(cuò)誤信息

  1. 當(dāng)事情達(dá)到已決階段后,通常需要進(jìn)行后續(xù)處理,不同的已決狀態(tài),決定了不同的后續(xù)處理
  • resolved狀態(tài):這是一個(gè)正常的已決狀態(tài),后續(xù)處理表示為thenable
  • rejected狀態(tài):這是一個(gè)非正常的已決狀態(tài),后續(xù)處理表示為catchable

后續(xù)處理可能有多個(gè),因此會(huì)形成作業(yè)隊(duì)列,這些后續(xù)處理會(huì)按照順序,當(dāng)狀態(tài)到達(dá)后依次執(zhí)行

Promise的基本使用

const pro = new Promise((resolve, reject)=>{
// 未決階段的處理
// 通過調(diào)用resolve函數(shù)將Promise推向已已決階段的resolved狀態(tài)
// 通過調(diào)用reject函數(shù)將Promise推向已決階段的rejected狀態(tài)
// resolve和reject均可以傳遞最多一個(gè)參數(shù),表示推向狀態(tài)的數(shù)據(jù)
})

pro.then(data => {
// 這是thenable函數(shù),如果當(dāng)前的Promise已經(jīng)是resolved狀態(tài),該函數(shù)會(huì)立即執(zhí)行
// 如果當(dāng)前是未決階段,則會(huì)加入到作業(yè)隊(duì)列,等待到達(dá)resolved狀態(tài)后執(zhí)行
// data為狀態(tài)數(shù)據(jù)
}, err => {
// 這是catchable函數(shù),如果當(dāng)前的Promise已經(jīng)是rejected狀態(tài),該函數(shù)會(huì)立即執(zhí)行
// 如果當(dāng)前是未決階段,則會(huì)加入到作業(yè)隊(duì)列,等待到達(dá)rejected狀態(tài)后執(zhí)行
// err為狀態(tài)數(shù)據(jù)
})

細(xì)節(jié)

  1. 未決階段的處理函數(shù)是同步的,會(huì)立即執(zhí)行
  2. thenablecatchable函數(shù)是異步的,就算是立即執(zhí)行,也會(huì)加入到事件隊(duì)列中等待執(zhí)行,并且,加入的隊(duì)列是微隊(duì)列
  3. pro.then可以只添加thenable函數(shù),pro.catch可以單獨(dú)添加catchable函數(shù)
  4. 在未決階段的處理函數(shù)中,如果發(fā)生未捕獲的錯(cuò)誤,會(huì)將狀態(tài)推向rejected,并會(huì)被catchable捕獲
  5. 一旦狀態(tài)推向了已決階段,無法再對狀態(tài)做任何更改
  6. Promise并沒有消除回調(diào),只是讓回調(diào)變的可控

Promise的串聯(lián)

當(dāng)后續(xù)的Promise需要用到之前的Promise的處理結(jié)果時(shí),需要Promise串聯(lián)

Promise對象中,無論時(shí)then方法還是catch方法,它們都具有返回值,返回的是一個(gè)全新的Promise對象,它的狀態(tài)滿足下面的規(guī)則:

  1. 如果當(dāng)前的Promise是未決的,得到的新的Promise是掛起狀態(tài)
  2. 如果當(dāng)前的Promise是已決的,會(huì)運(yùn)行響應(yīng)的后續(xù)處理函數(shù),并將后續(xù)處理函數(shù)的結(jié)果(返回值)作為resolved狀態(tài)數(shù)據(jù),應(yīng)用到新的Promise中;如果后續(xù)處理函數(shù)發(fā)生錯(cuò)誤,則把返回值作為rejected狀態(tài)數(shù)據(jù),應(yīng)用在新的Promise中。

后續(xù)的Promise一定會(huì)等到前面的Promise有了后續(xù)處理結(jié)果后,才會(huì)變成已決狀態(tài)

Promise 的串聯(lián)

原型成員(實(shí)例成員)

  • then:注冊一個(gè)后續(xù)處理函數(shù),當(dāng) Promiseresolved 狀態(tài)時(shí)運(yùn)行該函數(shù)
  • catch:注冊一個(gè)后續(xù)處理函數(shù),當(dāng) Promiserejected 狀態(tài)運(yùn)行該函數(shù)
  • finally:注冊一個(gè)后續(xù)處理函數(shù)(無參),當(dāng) Promise 為已決時(shí)運(yùn)行該函數(shù)

構(gòu)造函數(shù)成員(靜態(tài)成員)

  • resolve(數(shù)據(jù)):該方法返回一個(gè) resolved狀態(tài)的 Promise,傳遞的數(shù)據(jù)作為狀態(tài)數(shù)據(jù)

    • 特殊情況:如果傳遞的數(shù)據(jù)是 Promise,則直接返回傳遞的 Promise 對象
  • reject(數(shù)據(jù)):該方法返回一個(gè)rejected狀態(tài)的 Promise,傳遞的數(shù)據(jù)作為狀態(tài)數(shù)據(jù)

  • all(iterable) 這個(gè)方法返回一個(gè)新的 Promise 對象,該 Promise 對象在 iterable 參數(shù)對象里所有的 Promise 對象都成功的時(shí)候才會(huì)觸發(fā)成功,一旦有任何一個(gè) iterable 里面的 Promise 對象失敗則立即觸發(fā)該P(yáng)romise 對象的失敗。這個(gè)新的 Promise 對象在觸發(fā)成功狀態(tài)以后,會(huì)把一個(gè)包含 iterable 里所有 Promise 返回值的數(shù)組作為成功回調(diào)的返回值,順序跟 iterable 的順序保持一致;如果這個(gè)新的 promise 對象觸發(fā)了失敗狀態(tài),它會(huì)把 iterable 里第一個(gè)觸發(fā)失敗的 Promise 對象的錯(cuò)誤信息作為它的失敗錯(cuò)誤信息。Promise.all方法常被用于處理多個(gè) Promise 對象的狀態(tài)集合。

  • race(iterable):當(dāng) iterable 參數(shù)里的任意一個(gè)子 Promise 被成功或失敗后,父 Promise 馬上也會(huì)用子 Promise 的成功返回值或失敗詳情作為參數(shù)調(diào)用父 Promise 綁定的相應(yīng)句柄,并返回該 Promise 對象。

async 和 await

asyncawait 是ES2016新增的兩個(gè)關(guān)鍵字,它們借鑒了ES2015 中生成器的實(shí)際開發(fā)中的應(yīng)用,目的是簡化Promise API 的使用,并非是替代了 Promise

async

async用于修飾函數(shù)(無論是函數(shù)字面量還是函數(shù)表達(dá)式),放置在函數(shù)最開始的位置, 被修飾的函數(shù)返回結(jié)果一定是 Promise 對象。

async function test () {
    console.log(1);
    return 2
}

// 等同于

function test () {
    return new Promise((resolve, reject) => {
        console.log("promise")
        resolve(2)
    })
}

await

await關(guān)鍵字必須出現(xiàn)在async函數(shù)中

await用在某個(gè)表達(dá)式之前,如果表達(dá)式是一個(gè)Promise,則得到的是thenable中的狀態(tài)數(shù)據(jù)

async function test () {
    console.log(1)
    return 2
}

async function test1 () {
    const result = await test()
    console.log(result)
}

test1()
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容