1. 程式人生 > 前端設計 >一文讀懂Promise

一文讀懂Promise

什麼是Promise

$.ajax({
	success: (res) => {
		$.ajax({
            success: (res) => {
				$.ajax({
                    success: (res) => {
						//...
                    }
                })
            }
        })
	}
})
複製程式碼

這就是典型的回撥地獄,不僅程式碼臃腫,可讀性差,而且耦合度過高,不易維護。程式碼無法複用,還容易隱藏bug。

Promise規範的出現就是為了解決這種問題。

這裡強調一下,Promise是一種解決方案,它是一種規範。

ES6原生提供了Promise物件,在日常開發中經常會接觸到Promise相關操作,本文將介紹Promise的使用方法及相關原理,希望能有所幫助。

Promise有三種狀態:

  • Pending(等待態)
  • Fulfilled(成功態)
  • Rejected(失敗態)

Promise的特點是狀態只能由Pending轉換為Fulfilled或Rejected,並且一旦改變不再更改。

new Promise((resolve,reject) => {
	/*executor*/
})
複製程式碼

Promise是一個帶有resolve,reject

兩個引數的建構函式(executor)。這個建構函式在Promise建立的時候立即執行,resolve和reject兩個函式在被呼叫時,分別將Promise的狀態更改為fulfilled(成功態)和rejected(失敗態),並傳遞成功的值或失敗的原因。executor內部通常會執行一些非同步操作,當非同步操作執行完畢時,可根據執行結果相應地呼叫resolve或reject(可能成功可能失敗)。如果executor丟擲一個錯誤,那麼該Promise也將轉為失敗態。

Promise原型上還具有then和catch方法,呼叫後返回一個Promise例項,因此可以被鏈式呼叫。

每一個Promise例項都帶有一個then方法,該方法有兩個引數(onFulfilled

,onRejected),分別為Promise成功和失敗時的回撥。

let p = new Promise((resolve,reject) => {
	resolve('success');
})
p.then(value => {
	console.log(value);		//success
}).then(value => {
    console.log(value);		//沒有返回值時,undefined
})
複製程式碼

當返回了一個值或者一個成功態的Promise時,then返回的Promise都是成功態,若沒有返回值時也是成功態,而回調函式的引數值為undefind

當丟擲一個錯誤或者返回一個失敗態Promise,then返回的Promise都是失敗態。

let p = new Promise((resolve,reject) => {
	reject('fail');
})
p.then(() => {
	
},err => {
	console.log(err);		//fail
});
複製程式碼

then方法傳遞的成功/失敗函式,這兩個函式可以返回一個promise;如果返回的是一個promise,則該promise狀態將作為下一次then的結果。

let p = new Promise((resolve,reject) => {
	resolve('success')
});
let p2 = new Promise((resolve,reject) => {
    resolve('success2')
})
p.then(value => {
    return p2;				//p2的成功態將作為下一次then的狀態
}).then(value => {
    console.log(value)
})
複製程式碼

Promise.prototype.catch方法返回也是一個Promise,使用方式同then方法,用於處理異常捕獲情況。該方法捕獲屬於就近捕獲原則,離錯誤最近的一個catch將會捕獲錯誤並進行處理,後續將不再捕獲。

若失敗狀態被then方法中失敗函式回撥觸發,則catch方法將不進行捕獲。

let p1 = new Promise((resolve,reject) => {
	resolve('success');
});
p1.then(value => {
    throw 'a new error';
}).catch(e => {
    console.log(e);		//a new error
}).then(() => {
    console.log('after a catch the chain is restored');
})
複製程式碼

Promise.all方法返回一個新的Promise物件,在引數物件中所有的promise物件都成功時才會觸發成功態,一旦有任意一個物件失敗則立即觸發該Promise物件的失敗態。當觸發成功態後,會把一個包含所有Promise返回值的陣列按順序返回。如果觸犯失敗態,則會把第一個失敗的Promise物件錯誤資訊返回。

let p1 = new Promise((resolve,reject) => {
    setTimeout(() => {
        resolve('p1')
    },0)
});
let p2 = new Promise((resolve,reject) => {
    setTimeout(() => {
        resolve('p2')
    },0)
});
let promise = Promise.all([p1,p2]).then(value => {
    console.log(value);		//['p1','p2']
})
複製程式碼

Promise.race方法是把引數中任意第一個完成狀態轉換的Promise作為成功或失敗狀態,並返回該Promise物件。

let p1 = new Promise((resolve,1000)
});
let p2 = new Promise((resolve,reject) => {
    setTimeout(() => {
        reject('p2')
    },0)
});
Promise.race([p1,p2]).then((value) => {
    console.log(value)
},(err) => {
    console.log(err);		//p2
})
複製程式碼

Promise.resolve(value)返回一個成功態,並且將value傳遞給對應的then方法;

Promise.reject(reason)返回一個失敗態,並且將reason傳遞個對應的then方法。

Promise的優缺點

優點 缺點
解決回撥 無法監測進行狀態
鏈式呼叫 新建立即執行且無法取消
減少巢狀 內部錯誤無法丟擲

Promise練習(Node環境)

1.等待狀態

const promise1 = new Promise((resolve,reject) => {
    setTimeout(() => {
        resolve('success')
    },1000)
})
const promise2 = promise1.then(() => {
    return 'an error'
})

console.log('promise1',promise1)
console.log('promise2',promise2)

setTimeout(() => {
    console.log('promise1',promise1)
    console.log('promise2',promise2)
},2000)
複製程式碼

2.狀態改變

const promise = new Promise((resolve,reject) => {
    resolve('success1')
    reject('error')
    resolve('success2')
})
promise.then((res) => {
	console.log('then: ',res)
})
.catch((err) => {
	console.log('catch: ',err)
})
複製程式碼

3.鏈式呼叫

Promise.resolve(1)
    .then((res) => {
        console.log(res)
        return 2
    })
    .catch((err) => {
        return 3
    })
    .then((res) => {
        console.log(res)
    })
複製程式碼

4.鏈式呼叫

const promise = new Promise((resolve,reject) => {
    setTimeout(() => {
        console.log('executor')
        resolve('success')
    },1000)
})

const start = Date.now()
promise.then((res) => {
    console.log(res,Date.now() - start)
})
promise.then((res) => {
    console.log(res,Date.now() - start)
})
//Promise的建構函式只執行一次,並且立即執行;promise的then方法可以被多次呼叫,每次都返回新的promise例項。
複製程式碼
const promise = new Promise((resolve,reject) => {
	setTimeout(() => {
        console.log('executor')
        resolve('success')
    },1000)
})
const promise2 = promise.then(res => {
    console.log(res);
})
promise2.then(res => {
    console.log(res)
})
複製程式碼

5.錯誤捕獲

Promise.resolve()
    .then(() => {
        return new Error('error!!!')
    })
    .then((res) => {
        console.log('then: ',res)
    })
    .catch((err) => {
        console.log('catch: ',err)
    })
複製程式碼
Promise.resolve()
    .then(() => {
        throw new Error('error');		//return Promise.reject('error')
    })
    .then((res) => {
        console.log('then: ',err)
    })
複製程式碼

then或catch方法返回一個錯誤物件並不會被捕獲,只有在丟擲錯誤或返回失敗態時才會引起捕獲。

6.引用錯誤

const promise = Promise.resolve()
  .then(() => {
    return promise
  })
promise.catch(e => {
	console.log(e)
})
//[TypeError: Chaining cycle detected for promise #<Promise>]
複製程式碼

返回值不能是promise例項本身,因為此時例項並未構建完成,造成死迴圈,因此丟擲型別錯誤。

7.事件環

process.nextTick(() => {
    console.log('nextTick')
})
Promise.resolve()
    .then(() => {
        console.log('then')
    })
setImmediate(() => {
    console.log('setImmediate')
})
console.log('end')

複製程式碼

8.穿透

Promise.resolve(1)
    .then(2)
    .then(Promise.resolve(3))
    .then(console.log)
複製程式碼

then和catch方法的引數是一個函式,若非函式則發生穿透

Promise A+規範

規範:promisesaplus.com/

該網站介紹了若需要實現Promise需要實現的規範方法。當多個庫實現自己的Promise類時,為了能實現相應的效果,該規範提供了實現標準,並提供了promises-aplus-tests測試包測試實現能力。

後續將推出文章介紹如何實現自己的Promise類,敬請期待!

前端優選