=========================事件循環==================
先看以下的例子:
例1:
console.log("a");
setTimeout(() => {
console.log("b");
}, 0)
console.log("c");
// 以上的結果是 先輸出 a, c, b
// 然後看下面的例子:
console.log("a");
setTimeout(() => {
console.log("b");
}, 0)
for(let i = 0; i < 1000; i ++){
console.log("c")
}
// 以上的結果是 a 1000個c 最後是 b
問:爲什麼總都是b在最後呢?
事件回顧:
JS運行的環境稱之爲宿主環境。
執行棧:call stack , 一個數據結構, 用於存放各種函數的執行環境,每一個函數執行之前,
它的相關信息會加入到執行棧。 函數調用之前, 創建執行環境, 然後加入到執行棧; 函數調用之後,銷燬執行環境
JS引擎執行的都是棧的最頂部,執行完後,頂部的執行上下文會銷燬(出棧),
調用函數之前一定要先入棧(創建一個對應的上下文),執行完後出棧,銷燬對應的上下文
異步函數: 某些函數不會立即執行,需要等到某個時機到達後纔會執行,這樣的函數稱爲
異步函數,比如:事件處理函數,setTimout,setTimeItervel等。異步函數的執行時機,會被宿主環境控制。
瀏覽器宿主環境中包含5個線程:
1. JS引擎: 負責執行棧的最頂部代碼
2. GUI線程: 負責渲染頁面
3. 事件監聽線程: 負責監聽各種事件
4. 計時線程: 負責計時, 如setTimeOut, setTimeInterval等
5. 網絡線程: 負責網絡通信, 如 ajax, axios等
當上面的線程發生某些事情,如果該線程發現,這件事情有處理程序,他會將該處理程序加入
到一個叫做事件隊列的內存中。 當JS引擎發現,執行棧中已經沒有了任何內容後,會將事件隊列中
的第一個函數加入到執行棧中執行。
JS引擎對事件隊列的取出方式,以及與宿主環境的配合,稱之爲事件循環。
事件隊列在不同的宿主環境中有所差異,大部分宿主環境會將事件隊列進行細分。
在瀏覽器中,事件隊列分爲兩種:
宏隊列: macroTask, 計時器結束的回調,事件回調,http回調等絕大部分異步函數進入宏隊列
微隊列: mutationObserver, Promise產生的回調進入微隊列
mutationObserver: 用於監聽dom裏面屬性或者結構發生變化時,dom發生變化。
當執行棧清空時, JS引擎首先會將微隊列中的所有任務依次執行結束,如果沒有微隊列的任務,則執行宏隊列裏面的任務
=================es6異步處理 Promise=================
事件和回調函數的缺陷:
我們習慣於使用傳統的回調或事件處理來解決回調
事件: 某個對象的屬性是一個函數,當發生某一事件時,運行該函數
dom.onclick = function(){}
回調: 運行某個函數以實現某個功能的時候,傳入一個函數作爲參數,當發生某件事的時候,會運行該函數
dom.addEventLinster("click",function(){})
本質上,事件和回調並沒有本質的區別,只是函數放置的位置不同而且。
該模式主要面臨以下問題:
1. 回調地獄:某個異步操作需要等待之前的異步操作完成,無論用回調還是事件,都會陷入不斷的嵌套
2. 異步之間的聯繫: 某個異步操作要等待多個異步操作的結果,對這種聯繫的處理,會讓代碼的複雜度劇增
異步處理的通用模型
ES 官方參考了大量的異步場景,總結一套異步的通過模型,該模型可以覆蓋幾乎所有的異步場景,甚至同步場景
值得注意的是,爲了兼容舊系統,ES6 並不打算拋棄過去的做法,只是基於該模型推出的一個權限的 API, 使用該APi, 讓異步處理更加的簡潔優雅
理解該API, 最重要的是,理解他的api
1. ES6 將某一件可能發生異步操作的事情,可以分爲兩個階段: unsettled 和 settle
unsettled: 未決階段,表示事情還在進行前提的處理,並沒有發生通向結果的那件事;
settled: 已決階段, 事情已經有了一個結果,不管這個結果是好是壞,整件事情無法逆轉;
事情總是從 未決階段 逐步發展到 已決階段的。 並且,未決階段擁有控制何時通向已決階段的能力
2. ES6 將事情劃分爲三種狀態: pending, resolved rejected
pending: 掛起,處於未決階段,則表示這件事情還在掛起(最終的結果還沒有出來)
resolved: 已處理, 已決階段的一種狀態,表示整件事情出現了結果,並且是正常邏輯進行下去的結果
rejected: 已拒絕, 已決階段的一種狀態,表示整件事情出現結果,並不是一個正常的結果,錯誤的結果
既然未決階段有權決定事情的走向,因此,未決階段可以決定事情最終的狀態!
我們 把事情變爲resolved狀態的過程叫做:resolve,推向該狀態時候,可能還會傳遞一些數據。
我們 把事情變爲rejected狀態的過程叫做:rejected,推向該狀態時候,可能還會傳遞一些數據,一些錯誤的信息。
無論是哪個階段,過程都是不可逆的
3. 當事情的處理到達已決狀態, 不同的狀態決定不同的處理
resolved狀態: 這是一個正常的已決的狀態,後續處理表示未thenable
rejected狀態:這個一個非正常的已決的狀態,後續處理表示未catchable
後續處理可能有多個,因此會形成作業隊列,這些後續處理按照順序,當狀態到達後依次執行
Promise API:
promise 不是消除回調,而是將回調用兩種狀態來返回
使用方法:
const pro = new Promise((resolve, reject) => {
// 未決階段,也可以理解爲等待階段,異步之前做的事情,代碼在這裏寫
// 通過調用resolve函數將Promise推向已決階段的resolve狀態(成功)
// 通過調用reject函數將Promise推向已經階段的reject狀態(失敗)
// resolve 和rejecte 均可以傳遞最多一個參數,表示推送狀態的數據
})
pro.then(data => {
// 這是thenable 函數, 如果當前的Promise已經是resolved狀態,該函數會立即執行,
// 如果當前是爲決狀態,則會加入到作業隊列,等待到達resolved狀態後執行
// data爲resolved 的狀態數據
}, err => {
// 這是catchable 函數, 如果當前的Promise已經是rejected狀態,該函數會立即執行
// 如果當前是爲決狀態,則會加入到作業隊列,等待到達rejected狀態後執行
// err 爲rejected 的狀態數據
})
細節:
1. 未決階段的函數的代碼是同步代碼的,會立即執行
2. thenable 和catchable 函數是異步的,就是放到立即執行,但是必須需要等到同步代碼執行完後,纔會執行,並且是加入的是微隊列裏面
3. pro.then() 可以單獨thenable的函數, 也可單獨添加catchable的函數, 如 pro.catch();
4. 在未決狀態中發生錯誤或者拋出錯誤,會將錯誤推向reject,並且會被catchable捕獲
5. 一旦狀態推向已決,狀態不可以改變
6. promise並沒有消除回調, 只是讓回調變的可控
=======================Promise 串聯======================
當後續的promise需要用到之前promise產生的結果;
Promise 無論then方法,還是catch方法, 返回的是一個全新的Promise對象,狀態滿足下列規則:
1. 返回的Promise對象如果是掛起狀態(未決),新的Promise的狀態也是掛起狀態
2. 如果當前的Promise是已決狀態, 會運行後續的函數,並將後續處理函數的結果(返回值)
作爲resolved狀態數據,會應用奧新的Promise中; 如果後續處理函數發生錯誤,則把返回值當作
rejected狀態數據,應用到新的Promise中(後續的Promise,需要等到前面的promise已決)
如果前面的Promise的後續處理,返回的是一個Promise, 那麼後面的Promise的狀態和前面的狀態信息保持一致
const pro = new Promise((resolve, reject) => {
resolve(1);
});
const pro1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(3);
}, 3000)
});
pro.then(result => {
console.log("第一個promise的狀態")
console.log(result) // 1
return pro1;
}).then(result => {
console.log(result) // 3 下面是undefined,是因爲這裏沒有返回
}).then(result => {
console.log(result) //undefined
})
// 輸出結果 3 和 undefined 在3秒後打印
Promise的其他api
原型成員(實例成員):
then: 註冊一個後續處理函數,當Promise爲resolved狀態時運行該函數
catch: 註冊一個後續處理函數, 當Promise爲rejected狀態時運行該函數
finally:[es2018] 註冊一個後續處理函數(無參),當Promise爲已決時運行該函數
例如:
const pro = new Promise((resolve, reject) => {
resolve(1)
});
pro.finally(() => {
console.log("第一次finally的執行");
})
pro.then(res => {
console.log(res, "已決resolved得出的結果")
})
pro.catch(err => {
console.log(err, "已決reject得出結果")
})
pro.finally(() => {
console.log("已決第二次執行finally")
})
// 得出結果如下:
// 第一次finally的執行
// 1 已決resolved得出的結果
// 已決第二次執行finally
構造函數成員(靜態成員)
resolve: 該方法返回一個resolved狀態的Promise,傳遞的數據作爲狀態數據;
特殊情況:如果傳遞的數據是promise, 則直接返回傳遞的Promise對象
例如:
const pro = new Promise((resolve, reject) => {
// 這裏面的代碼是同步代碼
resolve(1);
})
// 等效於
const pro = Promise.resolve(1);
特殊情況:
const pro = new Promise((resolve, reject) => {
// 這裏面的代碼是同步代碼
resolve(1);
})
const pro1 = Promise.resolve(pro);
// 等效於
const pro1 = pro;
reject: 該方法返回一個rejectd狀態的Promise,傳遞的數據作爲狀態數據
例如:
const pro = new Promise((resolve, reject) => {
// 這裏面的代碼是同步代碼
reject(1);
})
// 等效於
const pro = Promise.reject(1);
all(iterable): 這個方法返回一個新的Promise對象, 該promise對象在所有promise數組中
所有的promise都已決resolved的時候觸發成功方法, 一旦有任何一個promise裏面的已決rejected狀態
一行,會把錯誤立即返回;
例如:
function getRandom(min, max) {
return Math.floor(math.getRandom() * ((max - min) + min))
}
const proms = [];
for (let i = 0; i < 10; i++) {
proms.push(new Promise((resolve, rejecct) => {
setTimeout(() => {
resolve(i)
}, getRandom(1000, 5000));
}))
}
// 等待所有promise完成
Promise.all(proms).then(res => {
console.log("所有promise 已決resolved狀態後執行")
})
race: 有一個成功那就成功,有一個失敗那就失敗,返回的是一個Promise
es6 異步處理之 Promise學習總結
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.