這篇文章我們一起來學習如何使用`Promise`,以及如何實作一個自己的`Promise`,講解非常清楚,全程一步一步往後實作,附帶詳細注釋與原理講解。
前言
這篇文章我們一起來學習如何使用
Promise
,以及如何實作一個自己的
Promise
,講解非常清楚,全程一步一步往後實作,附帶詳細注釋與原理講解。
如果你覺的這篇文章有幫助到你,❤️關注+點贊❤️鼓勵一下作者,文章公衆号首發,關注 前端南玖 第一時間擷取最新的文章~
promise是什麼?主要用來解決什麼問題?
Promise是異步程式設計的一種解決方案,比傳統解決方案--回調函數和事件--更合理更強大。
Promise
特點:
(1)對象的狀态不受外界影響。
Promise
對象代表一個異步操作,有三種狀态:
pending
(進行中),
fulfilled
(已成功)和
reject
(已失敗)。隻有異步操作的結果,可以決定目前是哪一種狀态,任何其它操作都無法改變這個狀态。這也是
Promise
(承諾)這個名字的由來。
(2)一旦狀态改變,就不會再變,任何時候都可以得到這個結果。
Promise
對象的狀态改變,隻有兩種可能:從
pending
變為
fulfilled
和從
pending
rejected
。
promise主要用來解決:
- 回調地獄
- 并發請求
- 異步方案優化(但它本身不是異步的,new Promise()後,它會立即執行)
promise基本用法
ES6規定,
Promise
對象是一個構造函數,用來生成
Promise
執行個體
下面代碼創造了一個
Promise
const promise = new Promise(function(resolve,reject){
//...
if(/*異步操作成功*/){
resolve(value)
}else{
//異步操作失敗
reject(error)
}
})
Promise
構造函數接受一個函數作為參數,該函數的兩個參數分别是
resolve
和
reject
.他們是兩個函數,由JavaScript引擎提供,不用自己部署。
resolve
函數的作用是,将
Promise
對象的狀态從“未完成”變成“成功”(即從pending變為resolve),在異步操作成功時調用,并将異步操作的結果,作為參數傳遞出去;
reject
Promise
對象的狀态從“未完成”變成“失敗”(即從pending變為rejected),在異步操作失敗時調用,并将異步操作報出的錯誤,作為參數傳遞出去。
Promise
執行個體生成以後,可以用
then
方法分别指定
resolved
狀态和
rejected
狀态的回調函數,或用
catch
方法指定
rejected
狀态的回調函數。
promise.then(res=>{
//success
},error=>{
//error
}).catch(err=>{})
then
方法可以接受兩個回調函數作為參數,第一個回調函數是
Promise
對象的狀态變為
resolved
時調用,第二個回調函數是
Promise
rejected
時調用。其中,第二個函數是可選的,不一定要提供。這兩個函數都接收
Promise
對象傳出的值作為參數。
Ok,通過上面對promise基本用法的描述,我們大概知道了一個promise類裡面都應該包含哪些内容了:
- promise狀态:pending,fulfilled,rejected
- promise傳回值
- 執行器:promise執行入口(也就是你傳入的那個函數)
- resolve:改變promise狀态為fulfilled
- reject:改變promise狀态為rejected
- then:接收兩個回調,onFulfilled, onRejected。分别在promise狀态變為fulfiled或rejected後執行
- catch:接受一個回調,在promise狀态變為rejected後執行
簡單實作一個promise
我們知道了一個promise内容至少包含以上那些内容,是以一個簡單的promise内部至少是這樣的
class myPromise {
static PENDING = 'pending'
static FULFILLEd = 'fulfilled'
static REJECTED = 'rejected'
constructor(init){
this.state = myPromise.PENDING // promise狀态
this.promiseRes = null // promise傳回值
const resolve = result=>{
//...
}
const reject = result=>{
//...
}
try{
init(resolve,reject) // init就是初始化執行器
}catch(err){
reject(err)
}
}
then(onFulfilled,onRejected){
//...
}
catch(onRejected){
//...
}
}
OK,大概了解之後,我們再來一個一個的看裡面每個部分的實作以及作用
Promise的執行器
它其實是我們在
new Promise
時傳入的一個回調函數,這個函數本身是同步的,也就是說在
new Promise
時它就會執行,這也是我們操作promise的入口。
class myPromise{
//...
constructor(init){
try{
init(resolve,reject) // init就是初始化執行器
}catch(err){
reject(err) //這裡主要是在init執行器函數出錯時,用以讓promise狀态變為rejected
}
}
//...
}
該函數接受兩個回調函數(resolve,reject)作為參數,用以改變Promise的狀态
resolve與reject方法
這兩個函數作為參數傳到執行器函數中,用以後續改變Promise狀态
class myPromise {
static PENDING = 'pending'
static FULFILLEd = 'fulfilled'
static REJECTED = 'rejected'
constructor(init){
this.state = myPromise.PENDING // promise狀态
this.promiseRes = null // promise傳回值
this.resolveCallback = [] //成功回調集合
this.rejectCallback = [] //失敗回調集合
const resolve = result=>{
// 隻有當狀态為pending時才改變,保證狀态一旦改變就不會再變
if(this.state === myPromise.PENDING){
this.state = myPromise.FULFILLEd //改變狀态
this.promiseRes = result //傳回值
//依次調用成功回調
this.resolveCallback.forEach(fn=>fn())
}
}
const reject = result=>{
// 隻有當狀态為pending時才改變,保證狀态一旦改變就不會再變
if(this.state === myPromise.PENDING){
this.state = myPromise.REJECTED //改變狀态
this.promiseRes = result //傳回值
// 依次調用失敗回調
this.rejectCallback.forEach(fn=>fn())
}
}
try{
init(resolve,reject) // 注意this指向
}catch(err){
reject(err)
}
}
}
初步then方法
class myPromise {
static PENDING = 'pending'
static FULFILLEd = 'fulfilled'
static REJECTED = 'rejected'
constructor(init){
this.state = myPromise.PENDING // promise狀态
this.promiseRes = null // promise傳回值
this.resolveCallback = [] //成功回調集合
this.rejectCallback = [] //失敗回調集合
const resolve = result=>{
// 隻有當狀态為pending時才改變,保證狀态一旦改變就不會再變
if(this.state === myPromise.PENDING){
this.state = myPromise.FULFILLEd //改變狀态
this.promiseRes = result //傳回值
//依次調用成功回調
this.resolveCallback.forEach(fn=>fn())
}
}
const reject = result=>{
// 隻有當狀态為pending時才改變,保證狀态一旦改變就不會再變
if(this.state === myPromise.PENDING){
this.state = myPromise.REJECTED //改變狀态
this.promiseRes = result //傳回值
// 依次調用失敗回調
this.rejectCallback.forEach(fn=>fn())
}
}
try{
init(resolve,reject) // 注意this指向
}catch(err){
reject(err)
}
}
then(onFulfilled,onRejected){
if(this.state === myPromise.FULFILLEd && typeof onFulfilled === 'function') {
onFulfilled(this.promiseRes)
}
if(this.state === myPromise.REJECTED && typeof onRejected === 'function') {
onRejected(this.promiseRes)
}
}
}
寫到這裡,我們的promise已經初步成型了,我們可以來測試一下:
const res1 = new myPromise((res,rej)=>{
res('成功啦~')
rej('失敗啦~')
})
res1.then((res)=>{
console.log(res)
},err=>{
console.log(err)
})
// 按照預期,這裡應該是隻會列印出成功啦~
從上圖看我們,是不是符合我們的預期,并且
myPromise
内部與原生的
Promise
也是非常相似的。你們是不是覺得這裡已經沒問題了,上面我們隻是測了一下同步方法的執行,但别忘了,Promise主要是來解決異步問題的,我們再來試一下裡面執行異步方法還符不符合我們的預期?
const res1 = new myPromise((res,rej)=>{
setTimeout(()=>res('成功啦~'),1000)
// rej('失敗啦~')
})
res1.then((res)=>{
console.log(res)
})
這裡我們預期本來是一秒之後列印成功啦,但它并沒有如我們所願,反而是什麼也沒列印出來,這是因為在setTimeout執行之前(pen ding)這個then方法已經執行過了,1s後狀态變成fulfilled時,then也不會再執行了。
是以我們需要保證then方法的回調函數在promise狀态變成
fulfilled
或
rejected
時再執行,那麼當promise狀态為
pending
時我們先要把回調存在對應的隊列中,等後續狀态改變後再執行
較完整then方法
OK,這裡我們修改一下我們的then方法,讓其保證異步代碼執行的正确性 (具體實作微任務我們可以用 mutationObserver,這裡我們就用setTimeout來模拟一下)
class myPromise {
static PENDING = 'pending'
static FULFILLEd = 'fulfilled'
static REJECTED = 'rejected'
constructor(init){
this.state = myPromise.PENDING // promise狀态
this.promiseRes = null // promise傳回值
this.resolveCallback = [] //成功回調集合
this.rejectCallback = [] //失敗回調集合
const resolve = result=>{
// 隻有當狀态為pending時才改變,保證狀态一旦改變就不會再變
if(this.state === myPromise.PENDING){
this.state = myPromise.FULFILLEd //改變狀态
this.promiseRes = result //傳回值
//依次調用成功回調
this.resolveCallback.forEach(fn=>fn())
}
}
const reject = result=>{
// 隻有當狀态為pending時才改變,保證狀态一旦改變就不會再變
if(this.state === myPromise.PENDING){
this.state = myPromise.REJECTED //改變狀态
this.promiseRes = result //傳回值
// 依次調用失敗回調
this.rejectCallback.forEach(fn=>fn())
}
}
try{
init(resolve,reject) // 注意this指向
}catch(err){
reject(err)
}
}
then(onFulfilled,onRejected){
if(this.state === myPromise.FULFILLEd && typeof onFulfilled === 'function') {
onFulfilled(this.promiseRes)
}
if(this.state === myPromise.REJECTED && typeof onRejected === 'function') {
onRejected(this.promiseRes)
}
if(this.state === myPromise.PENDING){
if(onFulfilled && typeof onFulfilled === 'function'){
this.resolveCallback.push(()=>
// 這裡我們用setTimeout來模拟實作then的微任務
setTimeout(()=>{
onFulfilled(this.promiseRes)
},0)
)
}
if(onRejected && typeof onRejected === 'function'){
this.rejectCallback.push(()=>
// 這裡我們用setTimeout來模拟實作then的微任務
setTimeout(()=>{
onRejected(this.promiseRes)
},0)
)
}
}
}
}
這裡我們可以再測試一下上面那個異步函數的測試用例,發現它能夠正确列印,OK,一個較完整的then方法就算實作了~
then的鍊式調用
then
方法會傳回一個新的
Promise
(⚠️注意:不是原來的那個
Promise
)是以可以采用鍊式調用
采用鍊式的
then
,可以指定一組按照次序調用的回調函數。這時,前一個回調函數,有可能傳回的還是一個
Promise
對象(即有異步操作),這時後一個回調函數,就會等待該
Promise
對象的狀态發生變化,才會被調用。
then(onFulfilled,onRejected){
const {promiseRes,state} = this
let promise = new myPromise((reso,reje)=>{
const resolveMyPromise = promiseRes => {
try{
if(typeof onFulfilled !== 'function'){
// 如果then的第一個回調不是一個函數,直接忽略,傳回一個新的promise
reso(promiseRes)
}else{
// 擷取第一個回調的執行結果
const res = onFulfilled(promiseRes)
// 看該執行結果是否是一個promise
if(res instanceof myPromise){
// 是一個promise,等它狀态改變後再改變then傳回的promise狀态
res.then(reso,rej)
}else{
// 不是一個promise,将它作為新的promise的resolve
reso(res)
}
}
}catch(err){
//異常,直接将新的promise狀态置為rejected
reje(err)
}
}
const rejectMyPromise = promiseRes => {
try{
if(typeof onRejected !== 'function'){
// 如果then的第二個回調不是一個函數,直接忽略,傳回一個新的promise
reje(promiseRes)
}else{
// 擷取第二個回調的執行結果
const res = onRejected(promiseRes)
// 看該執行結果是否是一個promise
if(res instanceof myPromise){
// 是一個promise,等它狀态改變後再改變then傳回的promise狀态
res.then(reso,rej)
}else{
// 不是一個promise,将它作為新的promise的resolve
reje(res)
}
}
}catch(err){
//異常,直接将新的promise狀态置為rejected
reje(err)
}
}
if(state === myPromise.FULFILLEd) {
resolveMyPromise(promiseRes)
}
if(state === myPromise.REJECTED) {
rejectMyPromise(promiseRes)
}
if(state === myPromise.PENDING){
if(onFulfilled && typeof onFulfilled === 'function'){
this.resolveCallback.push(()=>
// 這裡我們用setTimeout來模拟實作then的微任務
setTimeout(()=>{
resolveMyPromise(this.promiseRes)
},0)
)
}
if(onRejected && typeof onRejected === 'function'){
this.rejectCallback.push(()=>
// 這裡我們用setTimeout來模拟實作then的微任務
setTimeout(()=>{
rejectMyPromise(this.promiseRes)
},0)
)
}
}
})
return promise
}
catch方法
我們知道then的第二個回調其實與catch方法是一樣的,是以catch方法我們可以這樣實作
catch(onRejected) {
return this.then(undefined,onRejected)
}
Promise.resolve
将對象轉為一個promise對象,根據參數不通可分為四種情況
- 參數是一個Promise執行個體,直接傳回該執行個體
- 參數是一個
對象,将該對象轉為Promise對象後,執行該對象的thenable
方法then
- 沒有參數,也是傳回一個狀态為
的新的Promise對象resolved
- 參數是一個一個原始值,傳回一個新的Promise對象,狀态為
resolved
手動實作:
static resolve(v){
//1.參數是一個Promise執行個體,直接傳回
if(v instanceof myPromise){
return v
}
//2.參數是一個thenable對象,轉為Promise後執行該對象的then方法
if(typeof v === 'object' && typeof v.then === 'function'){
return new myPromise((res,rej)=>{
v.then(res,rej)
})
}
//3.沒有參數,直接傳回一個resolved狀态的promise
if(!v){
return new myPromise(res=>{
res()
})
}
//4.參數是一個原始值,傳回一個新的Promise,狀态為resolved
return new myPromise(res=>{
res(v)
})
}
Promise.reject
傳回一個新的Promise對象,狀态為
rejected
static reject(v){
return new myPromise((res,rej)=>{
rej(v)
})
}
Promise.all
該方法用于将多個Promise執行個體包裝成一個新的Promise執行個體,如果有不是Promise的項,則讓該項直接成功
用法:
const p = Promise.all([p1,p2,p3])
p
的狀态由
p1
、
p2
p3
決定,分成兩種情況。
(1)隻有
p1
p2
p3
的狀态都變成
fulfilled
,
p
的狀态才會變成
fulfilled
,此時
p1
p2
p3
的傳回值組成一個數組,傳遞給
p
的回調函數。
(2)隻要
p1
p2
p3
之中有一個被
rejected
p
的狀态就變成
rejected
,此時第一個被
reject
的執行個體的傳回值,會傳遞給
p
Ok,了解完
Promise.all
我們動手來實作一遍
static all (promises){
return new myPromise((res,rej)=>{
let count = 0
const result = [];
function addFun(index,resf) {
result[index]=resf // 這裡用索引别用push,保證傳回的順序
count++
if(count==promises.length) {
res(result)
}
}
[].forEach.call(promises,(promise,index)=>{
if(promise instanceof myPromise) {
promise.then(success=>{
// count ++
// result.push(success)
addFun(index,success)
},err=>{
rej(err)
})
}else{
addFun(index,promise)
}
})
})
}
Promise.race
Promise.race()
方法同樣是将多個 Promise 執行個體,包裝成一個新的 Promise 執行個體。
const p = Promise.race([p1, p2, p3]);
上面代碼中,隻要
p1
p2
p3
之中有一個執行個體率先改變狀态,
p
的狀态就跟着改變。那個率先改變的 Promise 執行個體的傳回值,就傳遞給
p
static race(promises) {
return new myPromise((res,rej)=>{
[].forEach.call(promises,promise=>{
if(promise instanceof myPromise){
promise.then(success=>{
res(success)
},error=>{
rej(error)
})
}else{
res(promise)
}
})
})
}
完整代碼
class myPromise {
static PENDING = 'pending'
static FULFILLEd = 'fulfilled'
static REJECTED = 'rejected'
constructor(init){
this.state = myPromise.PENDING // promise狀态
this.promiseRes = null // promise傳回值
this.resolveCallback = [] //成功回調集合
this.rejectCallback = [] //失敗回調集合
const resolve = result=>{
// 隻有當狀态為pending時才改變,保證狀态一旦改變就不會再變
if(this.state === myPromise.PENDING){
this.state = myPromise.FULFILLEd //改變狀态
this.promiseRes = result //傳回值
//依次調用成功回調
this.resolveCallback.forEach(fn=>fn())
}
}
const reject = result=>{
// 隻有當狀态為pending時才改變,保證狀态一旦改變就不會再變
if(this.state === myPromise.PENDING){
this.state = myPromise.REJECTED //改變狀态
this.promiseRes = result //傳回值
// 依次調用失敗回調
this.rejectCallback.forEach(fn=>fn())
}
}
try{
init(resolve,reject) // 注意this指向
}catch(err){
reject(err)
}
}
then(onFulfilled,onRejected){
const {promiseRes,state} = this
let promise = new myPromise((reso,reje)=>{
const resolveMyPromise = promiseRes => {
try{
if(typeof onFulfilled !== 'function'){
// 如果then的第一個回調不是一個函數,直接忽略,傳回一個新的promise
reso(promiseRes)
}else{
// 擷取第一個回調的執行結果
const res = onFulfilled(promiseRes)
// 看該執行結果是否是一個promise
if(res instanceof myPromise){
// 是一個promise,等它狀态改變後再改變then傳回的promise狀态
res.then(reso,rej)
}else{
// 不是一個promise,将它作為新的promise的resolve
reso(res)
}
}
}catch(err){
//異常,直接将新的promise狀态置為rejected
reje(err)
}
}
const rejectMyPromise = promiseRes => {
try{
if(typeof onRejected !== 'function'){
// 如果then的第二個回調不是一個函數,直接忽略,傳回一個新的promise
reje(promiseRes)
}else{
// 擷取第二個回調的執行結果
const res = onRejected(promiseRes)
// 看該執行結果是否是一個promise
if(res instanceof myPromise){
// 是一個promise,等它狀态改變後再改變then傳回的promise狀态
res.then(reso,rej)
}else{
// 不是一個promise,将它作為新的promise的resolve
reje(res)
}
}
}catch(err){
//異常,直接将新的promise狀态置為rejected
reje(err)
}
}
if(state === myPromise.FULFILLEd) {
resolveMyPromise(promiseRes)
}
if(state === myPromise.REJECTED) {
rejectMyPromise(promiseRes)
}
if(state === myPromise.PENDING){
if(onFulfilled && typeof onFulfilled === 'function'){
this.resolveCallback.push(()=>
// 這裡我們用setTimeout來模拟實作then的微任務
setTimeout(()=>{
resolveMyPromise(this.promiseRes)
},0)
)
}
if(onRejected && typeof onRejected === 'function'){
this.rejectCallback.push(()=>
// 這裡我們用setTimeout來模拟實作then的微任務
setTimeout(()=>{
rejectMyPromise(this.promiseRes)
},0)
)
}
}
})
return promise
}
catch(onRejected) {
return this.then(undefined,onRejected)
}
static all (promises){
return new myPromise((res,rej)=>{
let count = 0
const result = [];
function addFun(index,resf) {
result[index]=resf // 這裡用索引别用push,保證傳回的順序
count++
if(count==promises.length) {
res(result)
}
}
[].forEach.call(promises,(promise,index)=>{
if(promise instanceof myPromise) {
promise.then(success=>{
addFun(index,success)
},err=>{
rej(err)
})
}else{
addFun(index,promise)
}
})
})
}
static race(promises) {
return new myPromise((res,rej)=>{
[].forEach.call(promises,promise=>{
if(promise instanceof myPromise){
promise.then(success=>{
res(success)
},error=>{
rej(error)
})
}else{
res(promise)
}
})
})
}
static resolve(v){
//1.參數是一個Promise執行個體,直接傳回
if(v instanceof myPromise){
return v
}
//2.參數是一個thenable對象,轉為Promise後執行該對象的then方法
if(typeof v === 'object' && typeof v.then === 'function'){
return new myPromise((res,rej)=>{
v.then(res,rej)
})
}
//3.沒有參數,直接傳回一個resolved狀态的promise
if(!v){
return new myPromise(res=>{
res()
})
}
//4.參數是一個原始值,傳回一個新的Promise,狀态為resolved
return new myPromise(res=>{
res(v)
})
}
static reject(v){
return new myPromise((res,rej)=>{
rej(v)
})
}
}
總結
OK,上面跟大家一起過了一遍
Promise
的用法以及自己動手實作了一遍
Promise
,想必看完這篇文章,大家對
Promise
會有一個更加清晰的認識。
我是
南玖
,感謝各位的:「點贊和關注」,我們下期見!
作者:前端南玖
出處:https://www.cnblogs.com/songyao666/
-------------------------------------------
個性簽名:智者創造機會,強者把握機會,弱者坐等機會。做一個靈魂有趣的人!
如果覺得這篇文章對你有小小的幫助的話,可以關注下方公衆号,在該公衆号同樣會推送技術文章給大家,謝謝~
歡迎加入前端技術交流群:928029210