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狀態就會發生變化。
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)