Promise源碼分析
Promise的底層是瀏覽器實現的,所以我只是實現了裏的功能,並非和源碼一摸一樣。下面爲部分源碼,完整源碼移駕到github中下載:
https://github.com/young-monk/my-promise.git
我們首先來定義一個MyPromise類,我在這裏使用立即執行函數的方式,防止變量全局污染。
const MyPromise = (() => {
//在這裏定義內部變量
return class {
//構造器
constructor(executor) {
//...
}
})()
接下來,定義一些內部使用的變量:我這裏使用了Symbol
,使用Symbol
的原因是改變量只能內部使用
const MyPromise = (() => {
//在這裏定義內部變量
const PENDING = "pending"
const RESOLVED = "resolved"
const REJECTED = "rejected"
//使用Symbol是爲了僅供內部使用
const PromiseStatus = Symbol("PromiseStatus")//當前狀態
const PromiseValue = Symbol("PromiseValue")//當前值
return class {
//構造器
constructor(executor) {
//...
}
})()
我們都知道,當我們new Promise
的時候,會有一個狀態,會有值,還會傳入兩個函數,resolve
和reject
。那麼我們實現一下:
constructor(executor) {
//初始化
this[PromiseStatus] = PENDING //當前狀態
this[PromiseValue] = undefined //當前值
/**
* 定義 resolve 函數
* @param {*} data: 要返回的數據
*/
const resolve = (data) => {
//...
}
/**
* 定義reject函數
* @param {*} data: 要返回的數據
*/
const reject = (data) => {
//...
}
//執行
executor(resolve, reject)
}
接下來,我們就要實現resolve
和reject
函數的功能,其實這兩個函數的功能非常簡單,就是修改當前的狀態和值,並且如果有任務隊列,我們就執行任務隊列中的內容。我把這兩個函數的功能封裝一下:
/**
* 改變狀態的函數
* @param {*} data: 數據
* @param {*} status: 要改變的狀態,resolve 或 reject
* @param {*} queue: 任務隊列
*/
[changeStatus](data, status, queue) {
//如果已經是已決狀態,那麼直接結束
if (this[PromiseStatus] !== PENDING) return;
this[PromiseStatus] = status //修改當前狀態
this[PromiseValue] = data //修改值
//變成已決階段後,執行相應的隊列函數
queue.forEach(q => q(data))
}
//---- 在 resolve 函數中調用
this[changeStatus](data, RESOLVED, this[thenables])
//---- 在 reject 函數中調用
this[changeStatus](data, REJECTED, this[catchables])
接下來我們要實現then
和catch
這兩個後續處理函數,這兩個函數實現什麼功能想必大家都知道了,但我們要注意的是,如果調用then或者catch的時候,當前的狀態已經是已決狀態,那麼就要直接執行。如果不是,那麼就加入任務隊列中,我在構造器中已經聲明瞭 thenables
和 catchables
變量(then
和catch
函數的任務隊列)
//settled then處理函數
then(thenable, catchable) {
//每個then都要返回一個新的promise
return this[linkPromise](thenable, catchable)
}
//settled catch處理函數
catch (catchable) {
return this[linkPromise](undefined, catchable)
}
因爲功能相同,我進行了 2 次封裝,都是哪兩次封裝呢?這是第一次,也就是實現加入任務隊列的功能:
/**
* then和catch的處理函數,分爲兩種情況,如果當前已經是已決狀態,
* 那麼直接執行(此時直接執行也要加入事件隊列中,無法模擬微隊列,只能用宏隊列實現下),如果當前還是未決狀態,
* 那麼把當前的處理函數加入相應的任務隊列中
* @param {*} handler 處理函數
* @param {*} queue 處理隊列
*/
[settledHandler](handler, status, queue) {
//如果不是函數,那麼直接返回
if (typeof handler !== "function") return
if (this[PromiseStatus] === status) {
//如果已經是已決狀態,直接執行
setTimeout(() => {
handler(this[PromiseValue])
}, 0);
} else {
//處於未決狀態,加入任務隊列
queue.push(handler)
}
}
還有一次封裝,也就是我們上面調用的 linkPromise
函數,爲什麼要調用這個函數?因爲then
和catch
後續處理函數,都要返回一個新的Promise
,我們什麼時候知道,then 或 catch 函數執行了,該返回新的Promise呢?這裏是比較難的一部分,我們可以將 settledHandler
也就是執行處理函數的功能,反正創建一個新的 Promise中執行:
/**
* 用於創建一個新的Promise, 當我們調用then和catch處理函數時, 會返回一個新的Promise
* @param {*} thenable
* @param {*} catchable
*/
[linkPromise](thenable, catchable) {
/**
* 返回一個新的Promise的狀態處理,如果父級已經變爲已決狀態, 那麼新的Promise也是已決狀態
* @param {*} data
* @param {*} handler
* @param {*} resolve
* @param {*} reject
*/
function exec(data, handler, resolve, reject) {
try {
//獲取返回值
const res = handler(data)
//如果返回的是一個Promise,此時我們直接處理一下就可以
if (res instanceof MyPromise) {
res.then(data => resolve(data), err => reject(err))
} else {
//改變狀態,和修改值
resolve(res)
}
} catch (error) {
reject(error)
}
}
//返回新的Promise
return new MyPromise((resolve, reject) => {
//處理then的
this[settledHandler](data => {
//如果傳過來的thenable不是函數,那麼直接resolve下並結束
if (typeof thenable !== "function") {
resolve(data)
return
}
//我們把操作相同的提取封裝一下
exec(data, thenable, resolve, reject)
}, RESOLVED, this[thenables])
//處理catch的
this[settledHandler](data => {
//如果傳過來的thenable不是函數,那麼直接reject下並結束
if (typeof catchable !== "function") {
reject(data)
return
}
//我們把操作相同的提取封裝一下
exec(data, catchable, resolve, reject)
}, REJECTED, this[catchables])
})
}
如果你能看到這裏,說明你已經對Promise已經完全掌握了,下面實現的是幾個Promise的靜態成員:
all成員
/**
* 當數組中的每一個值都變爲resolved時,返回新的promise的值resolve爲一個數組,數組的內容爲proms每個Promise的結果,如果有一個變爲rejected, 那麼直接結束
* @param {*} proms 假定爲一個數組
*/
static all(proms) {
return new MyPromise((resolve, reject) => {
const results = proms.map(p => {
var obj = {
result: undefined,
isResolved: false
}
//判斷是否已經全部完成
p.then(data => {
obj.result = data
obj.isResolved = true
//得到是否有非resolve狀態的
const unResolved = results.filter(res => !res.isResolved)
if (unResolved.length === 0) {//如果沒有 那麼我們返回新的Promise
resolve(results.map(res => res.result))
}
//如果有一個reject狀態,那麼直接返回
}, err => reject(err))
return obj
})
console.log(results)
})
}
race成員
/**
* 當數組中有一個處於已決狀態,那麼結束
* @param {*} proms: 假定是一個數組
*/
static race(proms) {
return new MyPromise((resolve, reject) => {
proms.forEach(p => {
p.then(data => resolve(data), err => reject(err))
})
})
}
resolve和reject靜態成員
/**
* 返回一個resolved狀態的promise
* @param {*} data
*/
static resolve(data) {
//如果穿過來的是一個Promise,直接返回就可以
if (data instanceof MyPromise) {
return data
}
return new MyPromise(resolve => resolve(data))
}
/**
* 返回一個rejected狀態的promise
* @param {*} err
*/
static reject(err) {
return new MyPromise((resolve, reject) => {
reject(err)
})
}
Promise正確打開方式
1、異步處理的通用模型
ES6 將某一件可能發生異步操作的事情,分爲兩個階段:unsettled
和 settled
-
unsettled:未決階段,表示事情還在進行前期的處理,並沒有發生通向結果的那件事
-
settled:已決階段,事情已經有了一個結果,不管這個結果是好是壞,整件事情無法逆轉
異步操作總是從 未決階段 逐步發展到 已決階段的。並且,未決階段擁有控制何時通向已決階段的能力。
異步操作分成了三種狀態:pending
(進行中)、fulfilled
(已成功)和rejected
(已失敗)
- pending:進行中,處於未決階段,則表示這件事情還在進行(最終的結果還沒出來)
- fulfilled:已成功,已決階段的一種狀態,表示整件事情已經出現結果,並是一個可以按照正常邏輯進行下去的結果
- rejected:已失敗,已決階段的一種狀態,表示整件事情已經出現結果,並是一個無法按照正常邏輯進行下去的結果,通常用於表示有一個錯誤
一旦這種狀態改變,就會固定,不會再改變。是不可逆的Promise狀態的改變只有兩種情況:
- 1、從
pending
通過resolve()
變爲fulfilled
。 - 2、從
pending
通過reject()
變爲rejected
。
參考流程圖:
2、Promise及其基本使用
爲了解決地獄回調和異步操作之間的聯繫,ES6提出了一種異步編程大的解決方案Promise。但是Promise並沒有消除回調,只是讓回調變得可控。
Promise是一個對象,它可以獲取異步操作的消息。爲了方便和簡易,下面的resolved
統指fulfilled
狀態
const pro = new Promise((resolve, reject)=>{
/*
未決階段的處理
通過調用resolve函數將Promise推向已決階段的resolved狀態
通過調用reject函數將Promise推向已決階段的rejected狀態
resolve和reject均可以傳遞最多一個參數,表示推向狀態的數據
*/
if(true){
resolve()
}else{
reject()
}
})
pro.then(data=>{
/*
這是thenable函數,如果當前的Promise已經是resolved狀態,該函數會立即執行
如果當前是未決階段,則會加入到作業隊列,等待到達resolved狀態後執行
data爲狀態數據
*/
},err=>{
/*
then函數也可以填catchable函數,也可以不填。我們最好是通過catch方法添加catchable函數
*/
})
pro.catch(err=>{
/*
這是catchable函數,如果當前的Promise已經是rejected狀態,該函數會立即執行
如果當前是未決階段,則會加入到作業隊列,等待到達rejected狀態後執行
err爲狀態數據
*/
})
注意:
-
Promise創建後會立即執行。
-
thenable
和catchable
函數是異步的,就算是立即執行,也會加入到事件隊列中等待執行,加入的隊列是微隊列。 -
在未決階段的處理函數中,如果發生未捕獲的錯誤,會將狀態推向
rejected
,並會被catchable
捕獲。 -
一旦狀態推向了已決階段,無法再對狀態做任何更改。
-
Promise並沒有消除回調,只是讓回調變得可控。
const pro = new Promise((resolve,reject)=>{
console.log("a")
setTimeout(() => {
console.log("d")
}, 0);
resolve(1)
console.log("b")
})
pro.then(data=>{
console.log(data)
})
console.log("c")
//a b c 1 d
以上代碼,立即創建一個Promise函數,並且立即執行函數中的代碼,所以首先輸出a
,隨後將setTimeout
中代碼加入宏隊列,然後通過resolve()
將Promise
的狀態推向已決狀態,但是resolve也是異步的,他會加入到微隊列中等同步代碼執行完畢後再執行。隨後輸出b
,Promise
函數執行完後,又執行剩下的同步代碼輸出c
,同步代碼執行完畢後,執行微隊列中的輸出resolve
的結果值1
,再執行宏隊列中的setTimeout
,輸出b
。
3、Promise的方法then,catch,finally
(1)then()和catch()
then():註冊一個後續處理函數,當Promise爲resolved
狀態時運行該函數
catch():註冊一個後續處理函數,當Promise爲rejected
狀態時運行該函數
Promise對象中,無論是then
方法還是catch
方法,它們都具有返回值,返回的是一個全新的Promise對象,它的狀態滿足下面的規則:
- 如果當前的Promise是未決的,得到的新的Promise是進行中狀態
- 如果當前的Promise是已決的,會運行響應的後續處理函數,並將後續處理函數的結果(返回值)作爲
resolved
狀態數據,應用到新的Promise中;如果後續處理函數發生錯誤,則把返回值作爲rejected狀態數據,應用到新的Promise中。
注意:後續的Promise一定會等到前面的Promise有了後續處理結果後,纔會變成已決狀態
如果前面的Promise的後續處理,返回的是一個Promise,則返回的新的Promise狀態和後續處理返回的Promise狀態保持一致。
const pro1 = new Promise((resolve,reject)=>{
resolve(1)
})
console.log(pro1)
// 異步調用,
const pro2 = pro1.then(result => result *2)
console.log(pro2)//pro2是一個Promise對象,狀態是pending
pro2.then(result=>{console.log(result)},err=>console.log(err))
上述代碼,在執行console.log(pro2)
的時候是同步執行, 此時pro1.then()
還未執行完畢,所以promise
還是pending
狀態
const pro1 = new Promise((resolve,reject)=>{
throw 1
})
const pro2 = pro1.then(result => result *2,err=> err*3)
pro2.then(result=>{console.log(result*2)},err=>console.log(err*3))
//6
上述代碼,對於串聯的Promise,then和catch均返回一個全新的Promise,所以在pro1的catch執行時返回的pro2執行的是正常的,並非拋出錯誤。所以執行的爲err * 3
,result * 2
。
const pro1 = new Promise((resolve,reject)=>{
throw 1
})
const pro2 = pro1.then(result => result *2,err=> err*3)
pro1.catch(err=>err*2)
pro2.then(result=>{console.log(result*2)},err=>console.log(err*3))
上述代碼,每一次返回都是一個全新的Promise,所以pro1.catch(err=>err*2)
並沒有變量接收。
const pro1 = new Promise((resolve,reject)=>{
resolve(1)
})
const pro2 = new Promise((resolve,reject)=>{
resolve(2)
})
const pro3 = pro1.then(result=>{
console.log(`結果${result}`) //1
return pro2
})
//pro3的狀態是pending
pro3.then(result=>{
console.log(result)//2
})
上述代碼,第一個then
方法指定的回調函數,返回的是另一個Promise
對象。這時,第二個then
方法指定的回調函數,就會等待這個新的Promise
對象狀態發生變化後再執行
const pro1 = new Promise((resolve,reject)=>{
resolve(1)
})
const pro2 = new Promise((resolve,reject)=>{
setTimeout(() => {
resolve(2)
}, 3000);
})
pro1.then(result=>{
console.log(`結果${result}`) //1
return pro2
}).then(result=>{
console.log(result)//2
}).then(result=>{
console.log(result)//undefined
})
上面代碼,最後一個輸出undefined
是因爲第二個then
方法沒有返會值。
(2)finally()
finally
方法用於指定不管 Promise 對象最後狀態如何,都會執行的操作。該方法是 ES2018 引入標準的。
const pro1 = new Promise((resolve,reject)=>{
resolve(1)
})
pro1.then(result=>{
console.log(`結果${result}`) //結果1
return 3
}).then(result=>{
console.log(result)//3
}).catch(error=>{
console.log(error)
}).finally(()=>{
console.log("我一定會執行的")//我一定會執行的
})
4、Promise的靜態成員
(1)resolve()
該方法返回一個resolved
狀態的Promise,傳遞的數據作爲狀態數據。有時需要將現有對象轉爲 Promise 對象,Promise.resolve()
方法就起到這個作用。
const pro = Promise.resolve(1)
//等同於
const pro = new Promise((resolve,reject)=>{
resolve(1)
})
特殊情況:如果傳遞的數據是Promise,則直接返回傳遞的Promise對象
(2)reject()
該方法返回一個rejected
狀態的Promise,傳遞的數據作爲狀態數據
const pro = Promise.reject(1)
//等同於
const pro = new Promise((resolve,reject)=>{
reject(1)
})
(3)all()
Promise.all()
方法的參數可以不是數組,但必須具有 Iterator 接口。這個方法返回一個新的Promise對象,
返回的新的Promise對象的狀態分成兩種情況:
- 當參數中所有的Promise對象的狀態都變成
fulfilled
,返回的Promise狀態纔會變成fulfilled
。此時參數的返回值組成一個數組,傳遞給新Promise的回調函數。 - 當參數之中有一個被
rejected
,新Promise的狀態就變成rejected
,此時第一個被reject
的實例的返回值,會傳遞給新Promise的回調函數。
//p1, p2, p3均是promise對象
const pro = Promise.all([p1, p2, p3]);
上述代碼就是一個非常簡單的調用方法
function getRandom(min,max){
return Math.random() * (max-min) + min
}
const proms = []
for(let i=0;i<10;i++){
proms.push(new Promise((resolve,reject)=>{
setTimeout(() => {
if(Math.random()<0.1){
reject(i)
}else{
resolve(i)
console.log(i,"完成")
}
}, getRandom(1000,5000))
}))
}
const pro = Promise.all(proms)
pro.then(res=>{
console.log("全部完成",res)
},err=>{
console.log(err,"錯誤")
})
上面代碼是產生10個Promise對象,每個Promise對象都延遲時間1~5s中的隨機時間後將狀態推向已決,調用all方法,當10個Promise對象全部完成後再輸出,或有一個錯誤的時候,也輸出。
(4)race()
Promise.race()
方法同樣是將多個 Promise 對象,包裝成一個新的 Promise 對象。Promise.race()
方法的參數與Promise.all()
方法一樣。
當參數裏的任意一個Promise被成功或失敗後,新Promise馬上也會用參數中那個promise的成功返回值或失敗詳情作爲參數調用新promise綁定的相應句柄,並返回該promise對象
function getRandom(min,max){
return Math.random() * (max-min) + min
}
const proms = []
for(let i=0;i<10;i++){
proms.push(new Promise((resolve,reject)=>{
setTimeout(() => {
if(Math.random()<0.1){
reject(i)
}else{
resolve(i)
console.log(i,"完成")
}
}, getRandom(1000,5000))
}))
}
const pro = Promise.race(proms)
pro.then(res=>{
console.log("有完成的",res)
},err=>{
console.log(err,"有錯誤")
})
console.log(proms)
上述代碼和all()
例子一樣,只不過,當有一個完成或失敗的時候,就會執行pro
的then
或catch
回調函數
例子:異步加載圖片
const preloadImage = function (path) {
return new Promise(function (resolve, reject) {
const image = new Image();
image.onload = resolve();
image.onerror = reject();
image.src = path;
});
};
一旦圖片加載完成,就會Promise狀態就會發生變化。
5、async 和 await
async 和 await 是 ES2016 新增兩個關鍵字,它們借鑑了 ES2015 中生成器在實際開發中的應用,目的是簡化 Promise api 的使用,並非是替代 Promise。實際上就是生成器函數的一個語法糖。目的是簡化在函數的返回值中對Promise的創建。
(1)async
async
函數返回一個 Promise 對象,可以使用then
方法添加回調函數。當函數執行的時候,一旦遇到await
就會先返回,等到異步操作完成,再接着執行函數體內後面的語句。
async 用於修飾函數(無論是函數字面量還是函數表達式),放置在函數最開始的位置,被修飾函數的返回結果一定是 Promise 對象。
async function test(){
console.log(1);
return 2;//完成時狀態數據
}
//等同於
function test(){
return new Promise((resolve, reject)=>{
console.log(1);
resolve(2);
})
}
async function test(){
console.log(1)
return 2
}
const pro = test()
console.log(pro)//promise對象 promiseValue是2
注意:async
函數返回的 Promise 對象,必須等到內部所有await
命令後面的 Promise 對象執行完,纔會發生狀態改變,除非遇到return
語句或者拋出錯誤。也就是說,只有async
函數內部的異步操作執行完,纔會執行then
方法指定的回調函數。
(2)await
正常情況下,await
命令後面是一個 Promise 對象,返回該對象的結果。如果不是 Promise 對象,就直接返回對應的值。await
類似於生成器的yield
,當遇到await
的時候,就會等待await
後面的Promise對象執行完畢後再繼續執行下面代碼。
async function test(){
const namePro = await getName();//異步ajax
const passwordPro = await getPassword();
}
test()
函數執行過程中,會先等待getName()
執行完畢後,再執行getPassword()
注意:await
關鍵字必須出現在async
函數中
如果多個await
命令後面的異步操作,如果不存在繼發關係,最好讓它們同時觸發。
async function test(){
let namePro = getName();
let passwordPro = getPassword();
let name = await namePro;
let password = await passwordPro;
}
先讓getName()
和getPassword()
執行,然後再等待結果
await用在某個表達式之前,如果表達式是一個Promise,則得到的是thenable
中的狀態數據。
async function test1(){
console.log(1);
return 2;
}
async function test2(){
const result = await test1();
console.log(result);
}
test2();
//等同於
function test1(){
return new Promise((resolve, reject)=>{
console.log(1);
resolve(2);
})
}
function test2(){
return new Promise((resolve, reject)=>{
test1().then(data => {
const result = data;
console.log(result);
resolve();
})
})
}
test2();
如果await
的表達式不是Promise,則會將其使用Promise.resolve包裝後按照規則運行
async function test(){
const result = await 1
console.log(result)
}
//---等同
function test(){
return new Promise((resolve,reject)=>{
Promise.resolve(1).then(data =>{
const result = data
console.log(result)
resolve()
})
})
}