ES6學習筆記篇五之Promise

Promise概述

1、異步處理的通用模型

ES6 將某一件可能發生異步操作的事情,分爲兩個階段:unsettledsettled

  • 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創建後會立即執行。

  • thenablecatchable函數是異步的,就算是立即執行,也會加入到事件隊列中等待執行,加入的隊列是微隊列。

  • 在未決階段的處理函數中,如果發生未捕獲的錯誤,會將狀態推向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也是異步的,他會加入到微隊列中等同步代碼執行完畢後再執行。隨後輸出bPromise函數執行完後,又執行剩下的同步代碼輸出c,同步代碼執行完畢後,執行微隊列中的輸出resolve的結果值1,再執行宏隊列中的setTimeout,輸出b

3、Promise的方法then,catch,finally

(1)then()和catch()

then():註冊一個後續處理函數,當Promise爲resolved狀態時運行該函數

catch():註冊一個後續處理函數,當Promise爲rejected狀態時運行該函數

Promise對象中,無論是then方法還是catch方法,它們都具有返回值,返回的是一個全新的Promise對象,它的狀態滿足下面的規則:

  1. 如果當前的Promise是未決的,得到的新的Promise是進行中狀態
  2. 如果當前的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 * 3result * 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對象的狀態分成兩種情況:

  1. 當參數中所有的Promise對象的狀態都變成fulfilled,返回的Promise狀態纔會變成fulfilled。此時參數的返回值組成一個數組,傳遞給新Promise的回調函數。
  2. 當參數之中有一個被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()例子一樣,只不過,當有一個完成或失敗的時候,就會執行prothencatch回調函數

例子:異步加載圖片

const preloadImage = function (path) {
  return new Promise(function (resolve, reject) {
    const image = new Image();
    image.onload  = resolve();
    image.onerror = reject();
    image.src = path;
  });
};

一旦圖片加載完成,就會Promise狀態就會發生變化。

6、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()
        })
    })
}
最後附一個自己模仿封裝的Promise
const MyPromise = (()=>{
    const PENDING = "pending",
    RESOLVE = "resolve",
    REJECT = "reject",
    PromiseValue = Symbol("PromiseValue"),
    PromiseStatus = Symbol("PromiseStatus"),
    changeStatus = Symbol("changeStaus"),
    thenables = Symbol("thenables"),
    catchables = Symbol("catchables"),
    settleHandle = Symbol("settleHandle"),
    linkPromise = Symbol("linkPromise");
    return class{
        //改變狀態
        [changeStatus](newStatus,newValue,queue){
            if(this[PromiseStatus !== PENDING]) return
            this[PromiseStatus] = newStatus
            this[PromiseValue] = newValue       
           //執行相應隊列中的函數
           queue.forEach(handler => handler(newValue));

        }
        /*
            executor未決階段(pending狀態)下的處理函數
        */ 
        constructor(executor){
            this[PromiseStatus] = PENDING;
            this[PromiseStatus] = undefined;
            this[thenables] = [] //resolved後續處理函數的數組
            this[catchables] = []//rejected後續處理函數的數組
            const resolve = data=>{
                this[changeStatus](RESOLVE,data,this[thenables])
            }
            const reject = (reason)=>{
                this[changeStatus](REJECT,reason,this[catchables])
            }
            try{
                executor(resolve,reject)
            }catch(err){
                reject(err)
            }
        }
        // 處理後續處理函數 後續處理函數,需要立即執行的狀態,作業隊列
        [settleHandle](handler,isNow,queue){
           if(typeof handler !== "function")return
            if(this[PromiseStatus] === isNow){
                setTimeout(() => {
                    handler(this[PromiseValue])
                }, 0);
            }else{
                queue.push(handler)
            }
        }
        [linkPromise](thenable,catchable){
            return new MyPromise((resolve,reject)=>{
                this[settleHandle](data=>{
                    try {
                        const result = thenable(data)//得到當前Promise
                        resolve(result)

                    } catch (error) {
                        reject(error)
                    }
                    },RESOLVE,this[thenables])
                this[settleHandle](err=>{
                   try {
                    const result = catchable(err)
                    resolve(result)
                   } catch (error) {
                       reject(error)
                   }
                },REJECT,this[catchables])

            })
        }
        then(thenable,catchable){
            return this[linkPromise](thenable,catchable)
        }
        catch(catchable){
            return this[linkPromise](undefined,catchable)
        }

        static all(prms){
            return new MyPromise((resolve,reject)=>{

            })
        }
        static race(prms){}
        static reject(reason){
            if(reason instanceof MyPromise){
                return reason
            }else{
                return new MyPromise(reject=>{
                    reject(reason)
                })
            }
        }
        static resolve(data){
            if(data instanceof MyPromise){
                return data
            }else{
                return new MyPromise(resolve=>{
                    resolve(data)
                })
            }
        }

    }
})()
const pro = new MyPromise((resolve,reject)=>{
    console.log("未決階段")
    // throw new Error("111")
    resolve(1)
})
pro.then(data=>{
    console.log(data)
    return 234
}).then(data=>{
    console.log(data)
})

pro.catch(data=>{
    console.log(data)
})
pro.catch(data=>{
    console.log(data)
})
console.log(pro)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章