利用ES6進行Promise封裝總結
原生Promise解析
簡介
- promise是非同步程式設計的一種解決方案,比傳統的解決方案--回撥函式和事件--更合理和強大。
- promise簡單說就是一個容器,裡面儲存著某個未來才會結束的事件(通常是一個非同步操作)的結果,從語法上來說,Promise是一個物件,從它可以獲取非同步操作的訊息,Promise提供統一的API,各種非同步操作都可以用同樣的方法進行處理
特點
- 物件的狀態不受外界影響,Promise物件代表一個非同步操作,有三種狀態:Pendding、fulfilled、rejected。只有非同步操作的結果,可以決定當前是哪一種狀態,其他操作都無法改變這個狀態。
- 一旦狀態改變,就不會在變,任何時候都可以得到這個結果,只有兩種可能:從Pendding變為fulfilled和從Pendding變為rejected。只要這兩種情況發生,狀態就凝固了,會一直保持這個結果,這時就稱為resolved。
利用es6進行Promise封裝
處理同步任務
- 原生方法呼叫方式
new Promise((resolve,reject)=>{ resolve(1) }).then(res=>{ console.log(res) //1 })
- 同步封裝思考
1.由呼叫方式可見Promise是一個類 2.它接收一個回撥函式,這個回撥函式接受resolve和reject方法作為引數 3.當狀態改變後執行then方法,並將resolve或reject的結果作為then方法接受回撥函式的引數
class Mypromise{ constructor(callback){ this.status='pendding' //成功結果 this.s_res = null // 失敗結果 this.f_res = null callback((arg)=>{ // 使用箭頭函式this不會丟失 // 改變狀態為成功 this.status = 'fulfilled' this.s_res = arg },(arg)=>{ // 改變狀態為失敗 this.status = 'rejected' this.f_res = arg }) } then(onresolve,onreject){ if(this.status === 'fulfilled'){ // 當狀態為成功時 onresolve(this.s_res) }else if(this.status === 'rejected'){ // 當狀態為失敗時 onreject(this.f_res) } } }
處理非同步任務
- 原生呼叫方式
new Promise((resolve,reject)=>{ setTimeOut(()=>{ resolve(1) },1000) }).then(res=>{ console.log(res) })
- 非同步封裝思考
1.根據js執行機制,setTimeOut屬於巨集任務,then回撥函式屬於微任務,當主執行緒執行完成後,會從非同步佇列中 取出本次的微任務先執行。 2.也就是說,then方法執行時,狀態還沒有改變,所有我們需要將then方法執行的回撥儲存起來,等到非同步程式碼執行 完成後,在統一執行then方法的回撥函式
class Mypromise{ constructor(callback){ this.status='pendding' //成功結果 this.s_res = null // 失敗結果 this.f_res = null this.query = [] // ++ callback((arg)=>{ // 使用箭頭函式this不會丟失 // 改變狀態為成功 this.status = 'fulfilled' this.s_res = arg // 當狀態改變後,統一執行then方法的回撥 this.query.forEach(item=>{ item.resolve(arg) }) },(arg)=>{ // 改變狀態為失敗 this.status = 'rejected' this.f_res = arg // 當狀態改變後,統一執行then方法的回撥 this.query.forEach(item=>{ item.reject(arg) }) }) } then(onresolve,onreject){ if(this.status === 'fulfilled'){ // 當狀態為成功時 onresolve(this.s_res) }else if(this.status === 'rejected'){ // 當狀態為失敗時 onreject(this.f_res) }else{ // ++ 狀態沒有改變 this.query.push({ // 儲存回撥函式到佇列中 resolve:onresolve, reject:onreject }) } } }
處理鏈式呼叫
- 原生呼叫方式
new Promise((resolve,reject)=>{ resolve(1) }).then(res=>{ return res }).then(res=>{ console.log(res) })
- 鏈式呼叫思考
原生的Promise物件的then方法,返回的也是一個Promise物件,一個新的Promise才能支援鏈式呼叫 下一個then方法可以接受上一個then方法的返回值作為回撥函式的引數 主要考慮上一個then方法的返回值: 1.Promise物件/具有then方法的物件 2.其他值 第一個then方法返回一個Promise物件,它的回撥函式接受resFn和rejFN兩個回撥函式作為引數, 把成功狀態的處理封裝為handle函式,接受成功的結果作為引數 在handle函式,根據onresolve返回值的不同做出不同的處理
class Mypromise{ constructor(callback){ this.status='pendding' //成功結果 this.s_res = null // 失敗結果 this.f_res = null this.query = [] // ++ callback((arg)=>{ // 使用箭頭函式this不會丟失 // 改變狀態為成功 this.status = 'fulfilled' this.s_res = arg // 當狀態改變後,統一執行then方法的回撥 this.query.forEach(item=>{ item.resolve(arg) }) },(arg)=>{ // 改變狀態為失敗 this.status = 'rejected' this.f_res = arg // 當狀態改變後,統一執行then方法的回撥 this.query.forEach(item=>{ item.reject(arg) }) }) } then(onresolve,onreject){ return new Mypromise((resFN,rejFN)=>{ if(this.status === 'fulfilled'){ // 當狀態為成功時 handle(this.s_res) }else if(this.status === 'rejected'){ // 當狀態為失敗時 errBack(this.f_res) }else{ // ++ 狀態沒有改變 this.query.push({ // 儲存回撥函式到佇列中 resolve:onresolve, reject:onreject }) } function handle(value){ // 當then方法的onresolve方法有返回值時,儲存其返回值,沒有使用其儲存的值 let returnVal = onresolve instanceof Function && onresolve(value) || value // 如果onresolve方法返回的是promise物件,則呼叫其then方法 if(returnVal&&returnVal['then'] instanceof Function){ returnVal.then(res=>{ resFN(res) },err=>{ rejFN(err) }) }else{ resFN(returnVal) } } function errBack(reason){ if(onreject instanceof Function){ let returnVal = reject(reason) if(typeof returnVal !== 'undenfined' && returnVal['then'] instanceof Function){ returnVal.then(res=>{ resFN(res) },err=>{ rejFN(err) }) }else{ resFN(returnVal) } }else{ rejFN(reason) } } }) } }
Promise.all和Promise.race方法
- 原生呼叫方式
Promise.all方法接受一個數組,陣列中的每一項都是一個Promise例項,只有陣列中的所有Promise例項的狀態 都變為fulfilled時,此時整個狀態才會變成fulfilled,此時陣列中所有Promise例項的返回值組成一個新的陣列, 進行傳遞。 Promise.race方法和Promise.all方法一樣,如果不是Promise例項,就會先呼叫Promise.resolve方法,將引數 轉為Promise例項,在進行下一步處理。 只要陣列中有一個引數的狀態變為fulfilled就會進行傳遞
// 將現有物件轉換為Promise物件 Mypromise.resolve = (arg)=>{ if(typeof arg == 'undefined' || arg==null){ // 不帶有任何引數 return new Mypromise(resolve=>{ resolve(arg) }) }else if(arg instanceof Mypromise){ // 是一個Mypromise例項 return arg }else if(arg['then'] instanceof Function){ // 具有then方法的物件 return new Mypromise((resolve,reject)=>{ arg.then(res=>{ resolve(res) },err=>{ reject(err) }) }) }else{ // 引數不是具有then方法的物件,或根本不是物件 return new Mypromise(resolve=>{ resolve(arg) }) } } Mypromise.all = (arr)=>{ if(!Array.isArray(arr)){ throw new TypeError('引數必須是一個數組') } return new Mypromise((resolve,reject)=>{ let i=0,result=[] next() functon next(){ // 如果不是Mypromise例項需要轉換 Mypromise.resolve(arr[i]).then(res=>{ result.push(res) i++ if(i===arr.length){ resolve(result) }else{ next() } },reject) } }) } Mypromise.race = (arr)=>{ if(!Array.isArray(arr)){ throw new TypeError('引數必須是一個數組') } return new Mypromise((resolve,reject)=>{ let done = false arr.forEach(item=>{ Mypromise.resolve(item).then(res=>{ if(!done){ resolve(res) done = true } },err=>{ if(!done){ reject(res) done = true } }) }) }) }
處理Mypromise狀態確定不能改變的特性
在重寫callback中的resolve和reject方法執行前,先判斷狀態是否為'pendding'