本篇屬于本系列第七篇
第一篇:#深入學習JavaScript系列(一)—— ES6中的JS執(zhí)行上下文
第二篇:# 深入學習JavaScript系列(二)——作用域和作用域鏈
第三篇:# 深入學習JavaScript系列(三)——this
第四篇:# 深入學習JavaScript系列(四)——JS閉包
第五篇:# 深入學習JavaScript系列(五)——原型/原型鏈
第六篇: # 深入學習JavaScript系列(六)——對象/繼承
第七篇:# 深入學習JavaScript系列(七)——Promise async/await generator
Promise屬于js進階的內(nèi)容,我剛剛開始學習的時候 我是這樣理解的: Promise是ES6中原生的一個方法,類似一個容器,代表著未來要發(fā)生的某件事情,屬于異步操作的一種方法,這句話在我初學的時候聽起來也是迷迷糊糊的 。
在閱讀了很多篇文章后,發(fā)現(xiàn)promise的底層原理:callback回調(diào)函數(shù)+發(fā)布訂閱模式
一 Promise
1.1 Promise概述
(1) 什么是promsie?
說的通俗易懂一點:從語法上講:Promise是一個對象(類),從它可以獲取異步操作的內(nèi)容消息;從本意上講,它是承諾,承諾它過一段時間會給你一種結果,Promise有三種狀態(tài):pending(等待),fulfiled(成功),rejected(失敗);狀態(tài)一旦改變,就不會再變,創(chuàng)造promise實例后,他會立即執(zhí)行
如果還不明白,我把他打印出來:
從上圖中可以看到 promise是一個對象,本身就有all reject,resolve等方法;還能看到promise的原型上有then,finally等方法。這幾個方法也是我們后面需要研究和實現(xiàn)的點。
(2) promise怎么用?
來個簡單的例子:
let p = new Promise(function(resolve, reject){
//做一些異步操作
setTimeout(function(){
console.log('執(zhí)行完成Promise');
resolve('要返回的數(shù)據(jù)可以任何數(shù)據(jù)例如接口返回數(shù)據(jù)');
}, 2000);
});
promise接受一個函數(shù)參數(shù),此函數(shù)中傳入兩個參數(shù):resolve,reject,分別表示異步操作執(zhí)行成功后的回調(diào)函數(shù)和異步操作執(zhí)行失敗后的回調(diào)函數(shù)。
但是我們?nèi)粘J褂貌皇沁@直接new 一個promise的,而是會在外面包裹一個函數(shù),然后調(diào)用外面的函數(shù),這樣做的好處是我們return出去的依然是一個promise對象,還可以使用promise上的then,catch等方法。
function promiseFn(){
var p = new Promise(function(resolve, reject){
//做一些異步操作
setTimeout(function(){
console.log('執(zhí)行完成');
resolve('隨便什么數(shù)據(jù)');
}, 2000);
});
return p;
}
promiseFn()
promiseFn().then(function(data){
console.log(data);
// 其他操作
});
(3) promise有什么特點?
1、promise的狀態(tài)不受外界的影響,就像我開頭說的是一個容器,除了異步操作的結果其他手段無法改變promise的狀態(tài)。
2、狀態(tài)一旦改變 就不會改變,任何時候都會得到這個結果,狀態(tài)改變有兩種: 從pending變?yōu)閒ulfilled和從pending變?yōu)閞ejected
.
(4) promise解決了什么問題?:
promise解決了兩個主要的問題:
- 解決回調(diào)地獄:代碼難以維護,嵌套過多每一個函數(shù)的輸出是第二個函數(shù)的輸入這種現(xiàn)象
- 實現(xiàn)多個并發(fā)請求,獲取并發(fā)請求中的數(shù)據(jù),(解決異步問題)
在傳統(tǒng)的異步編程中,如果異步之間存在依賴關系,我們需要通過層層嵌套來實現(xiàn)這種依賴,如果嵌套層數(shù)過多,就會變成我們所說的回調(diào)地獄,promise可以把回調(diào)嵌套改成鏈式調(diào)用,
注: promise可以解決異步問題,但是不能說promise本身是異步的
通過上述幾個問題,應該基本能了解promsie了,下面就來說promsie中幾個重點
1.2 then
在上面的打印中我們可以知道 then是promsie原型鏈上的一個方法,
then其實就是解決promise中的回調(diào)問題的,promise通過then實現(xiàn)了鏈式調(diào)用,
在 then中有兩個參數(shù) :onFulfilled,onRejected
分別是成功的值,失敗的原因。
-
當狀態(tài)state為fulfilled,則執(zhí)行onFulfilled,傳入this.value。當狀態(tài)state為rejected,則執(zhí)行onRejected,傳入this.reason
-
onFulfilled,onRejected如果他們是函數(shù),則必須分別在fulfilled,rejected后被調(diào)用,value或reason依次作為他們的第一個參數(shù)
then 的使用:
function promiseFn(){
var p = new Promise(function(resolve, reject){
//做一些異步操作
setTimeout(function(){
console.log('執(zhí)行完成');
resolve('隨便什么數(shù)據(jù)');
}, 2000);
});
return p;
}
promiseFn()
promiseFn().then(function(data){
console.log(data);
// 其他操作
}).then(data=>{
console.log(data) //undefined
return [1,2,3]
}).then(data=>{
console.log(data) //[1,2,3]
})
那么then是怎么進行鏈式調(diào)用的呢?
- promise中使用.then()的方法來進行鏈式調(diào)用,通過.then()的回調(diào)拿到上一個.then()的返回值
- 鏈式調(diào)用要求.then()方法返回的必須是一個promise,這樣才有.then()方法;
- 需要按照順序執(zhí)行,所以只有當前的promise狀態(tài)變更后,才能執(zhí)行下一個then方法
1.3 如何手寫一個Promise
初步分析promise
首先我們來分析一個簡單的promise例子
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('result')
},
1000);
})
p1.then(res => console.log(res), err => console.log(err))
實際上 promise的整個執(zhí)行過程是:
- promise的構造方法接受一個executor(),在new Promise()時刻就立即執(zhí)行了executor()回調(diào),
- executor內(nèi)部的異步任務被放入宏/微任務隊列,等待執(zhí)行。也就是上面代碼中的定時器。
- 當then()被執(zhí)行時,收集回調(diào)(成功或者失?。?同時放入成功或者失敗隊列。
- executor ()異步任務被執(zhí)行,觸發(fā) resolve/reject 從成功/失敗中取出回調(diào)依次執(zhí)行
通過上面講解執(zhí)行過程可以看出 promise使用的是個觀察者模式,也就是收集依賴 -觸發(fā)通知-取出依賴執(zhí)行的方式
總結:在promise中 執(zhí)行順序是then收集依賴 -異步觸發(fā)resolve - resolve收集依賴
實現(xiàn)promise
在1.1中提到 promise本質(zhì)上是一個類,所以使用class來聲明promise
class MyPromise {
// 構造方法接收一個回調(diào)
constructor(executor) {
this._resolveQueue = [] // then收集的執(zhí)行成功的回調(diào)隊列
this._rejectQueue = [] // then收集的執(zhí)行失敗的回調(diào)隊列
// 由于resolve/reject是在executor內(nèi)部被調(diào)用,
// 因此需要使用箭頭函數(shù)固定this指向, 否則找不到this._resolveQueue
let _resolve = (val) => {
// 從成功隊列里取出回調(diào)依次執(zhí)行
while(this._resolveQueue.length) {
const callback = this._resolveQueue.shift()
callback(val)
}
}
// 實現(xiàn)同resolve
let _reject = (val) => {
while(this._rejectQueue.length) {
const callback = this._rejectQueue.shift()
callback(val)
}
}
// new Promise()時立即執(zhí)行executor,并傳入resolve和reject
executor(_resolve, _reject)
}
// then方法,接收一個成功的回調(diào)和一個失敗的回調(diào),并push進對應隊列
then(resolveFn, rejectFn) {
this._resolveQueue.push(resolveFn)
this._rejectQueue.push(rejectFn)
}
}
在上面的代碼中 我們通過構造方法 創(chuàng)建了兩個隊列:resolveQueue rejectQueue 用來收集執(zhí)行成功/失敗后的回調(diào)隊列, 然后依次判斷并取出作為resolve和reject的回調(diào) 最后在調(diào)用then方法的時候返回即可,著就是promise的基礎功能 實現(xiàn)了resolve 和rejcet
實現(xiàn)promiseA+
當然 我們還要繼續(xù)往下寫:在Es6中 Promise需要遵循 PromiseA+規(guī)范
,要求對promise的狀態(tài)進行控制 。
PromiseA+規(guī)范很多,核心規(guī)則有兩個:
- Promise的狀態(tài)有且只有三種: Pending(等待)。Fulfilled(已完成)。 Rejected(拒絕)。
- then有兩個參數(shù)可選,分別對應狀態(tài)改變時觸發(fā)的回調(diào),then方法返回一個promise 同時then方法可以被同一個promise調(diào)用很多次
加上promiseA+規(guī)范后,我們的代碼是這樣的:
//Promise/A+規(guī)范的三種狀態(tài)
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class MyPromise {
// 構造方法接收一個回調(diào)
constructor(executor) {
this._status = PENDING // Promise狀態(tài) 初始的時候
this._resolveQueue = [] // 成功隊列, resolve時觸發(fā)
this._rejectQueue = [] // 失敗隊列, reject時觸發(fā)
// 由于resolve/reject是在executor內(nèi)部被調(diào)用, 因此需要使用箭頭函數(shù)固定this指向, 否則找不到this._resolveQueue
let _resolve = (val) => {
if(this._status !== PENDING) return // 對應規(guī)范中的"狀態(tài)只能由pending到fulfilled或rejected"
this._status = FULFILLED // 變更狀態(tài)
// 這里之所以使用一個隊列來儲存回調(diào),是為了實現(xiàn)規(guī)范要求的 "then 方法可以被同一個 promise 調(diào)用多次"
// 如果使用一個變量而非隊列來儲存回調(diào),那么即使多次p1.then()也只會執(zhí)行一次回調(diào)
while(this._resolveQueue.length) {
const callback = this._resolveQueue.shift()
callback(val)
}
}
// 實現(xiàn)同resolve
let _reject = (val) => {
if(this._status !== PENDING) return // 對應規(guī)范中的"狀態(tài)只能由pending到fulfilled或rejected"
this._status = REJECTED // 變更狀態(tài)
while(this._rejectQueue.length) {
const callback = this._rejectQueue.shift()
callback(val)
}
}
// new Promise()時立即執(zhí)行executor,并傳入resolve和reject
executor(_resolve, _reject)
}
// then方法,接收一個成功的回調(diào)和一個失敗的回調(diào)
then(resolveFn, rejectFn) {
this._resolveQueue.push(resolveFn)
this._rejectQueue.push(rejectFn)
}
}
解釋:
- 先初始化的時候增加三種狀態(tài)
- 在進入constructor構造函數(shù)時把狀態(tài)設置為PENDING
- 在執(zhí)行_resolve 和_reject時需要判斷當前狀態(tài)是否為PENDING,如果不是直接return出去
- 在進入_resolve 和_reject流程后把狀態(tài)對應改為FULFILLED 和REJECTED。
實現(xiàn)then方法
接下來到了 鏈式調(diào)用;上面我們提到 promsie采用了鏈式調(diào)用的方法來改變回調(diào)地獄
前面分析過鏈式調(diào)用的步驟:
- promise中使用.then()的方法來進行鏈式調(diào)用,通過.then()的回調(diào)拿到上一個.then()的返回值
- 鏈式調(diào)用要求.then()方法返回的必須是一個promise,這樣才有.then()方法;
- 需要按照順序執(zhí)行,所以只有當前的promise狀態(tài)變更后,才能執(zhí)行下一個then方法
接下來就按照這個步驟實現(xiàn)then
注意: 在使用then方法的時候,我們需要判斷 傳入的參數(shù)是否是function;同時要判斷promise的三種狀態(tài)對應不同的寫法
// then方法,接收一個成功的回調(diào)和一個失敗的回調(diào)
then(resolveFn, rejectFn) {
// 根據(jù)規(guī)范,如果then的參數(shù)不是function,則我們需要忽略它, 讓鏈式調(diào)用繼續(xù)往下執(zhí)行
typeof resolveFn !== 'function' ? resolveFn = value => value : null
typeof rejectFn !== 'function' ? rejectFn = reason => {
throw new Error(reason instanceof Error? reason.message:reason);
} : null
// return一個新的promise
return new MyPromise((resolve, reject) => {
// 把resolveFn重新包裝一下,再push進resolve執(zhí)行隊列,這是為了能夠獲取回調(diào)的返回值進行分類討論
const fulfilledFn = value => {
try {
// 執(zhí)行第一個(當前的)Promise的成功回調(diào),并獲取返回值
let x = resolveFn(value)
// 分類討論返回值,如果是Promise,那么等待Promise狀態(tài)變更,否則直接resolve
x instanceof MyPromise ? x.then(resolve, reject) : resolve(x)
} catch (error) {
reject(error)
}
}
// reject同理
const rejectedFn = error => {
try {
let x = rejectFn(error)
x instanceof MyPromise ? x.then(resolve, reject) : resolve(x)
} catch (error) {
reject(error)
}
}
switch (this._status) {
// 當狀態(tài)為pending時,把then回調(diào)push進resolve/reject執(zhí)行隊列,等待執(zhí)行
case PENDING:
this._resolveQueue.push(fulfilledFn)
this._rejectQueue.push(rejectedFn)
break;
// 當狀態(tài)已經(jīng)變?yōu)閞esolve/reject時,直接執(zhí)行then回調(diào)
case FULFILLED:
fulfilledFn(this._value) // this._value是上一個then回調(diào)return的值(見完整版代碼)
break;
case REJECTED:
rejectedFn(this._value)
break;
}
})
}
promise+then實現(xiàn)
//Promise/A+規(guī)定的三種狀態(tài)
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class MyPromise {
// 構造方法接收一個回調(diào)
constructor(executor) {
this._status = PENDING // Promise狀態(tài)
this._value = undefined // 儲存then回調(diào)return的值
this._resolveQueue = [] // 成功隊列, resolve時觸發(fā)
this._rejectQueue = [] // 失敗隊列, reject時觸發(fā)
// 由于resolve/reject是在executor內(nèi)部被調(diào)用, 因此需要使用箭頭函數(shù)固定this指向, 否則找不到this._resolveQueue
let _resolve = (val) => {
//把resolve執(zhí)行回調(diào)的操作封裝成一個函數(shù),放進setTimeout里,以兼容executor是同步代碼的情況
const run = () => {
if(this._status !== PENDING) return // 對應規(guī)范中的"狀態(tài)只能由pending到fulfilled或rejected"
this._status = FULFILLED // 變更狀態(tài)
this._value = val // 儲存當前value
// 這里之所以使用一個隊列來儲存回調(diào),是為了實現(xiàn)規(guī)范要求的 "then 方法可以被同一個 promise 調(diào)用多次"
// 如果使用一個變量而非隊列來儲存回調(diào),那么即使多次p1.then()也只會執(zhí)行一次回調(diào)
while(this._resolveQueue.length) {
const callback = this._resolveQueue.shift()
callback(val)
}
}
setTimeout(run)
}
// 實現(xiàn)同resolve
let _reject = (val) => {
const run = () => {
if(this._status !== PENDING) return // 對應規(guī)范中的"狀態(tài)只能由pending到fulfilled或rejected"
this._status = REJECTED // 變更狀態(tài)
this._value = val // 儲存當前value
while(this._rejectQueue.length) {
const callback = this._rejectQueue.shift()
callback(val)
}
}
// 這里的定時器是為了 保證promise的執(zhí)行順序 下面的也是
setTimeout(run)
}
// new Promise()時立即執(zhí)行executor,并傳入resolve和reject
executor(_resolve, _reject)
}
// then方法,接收一個成功的回調(diào)和一個失敗的回調(diào)
then(resolveFn, rejectFn) {
// 根據(jù)規(guī)范,如果then的參數(shù)不是function,則我們需要忽略它, 讓鏈式調(diào)用繼續(xù)往下執(zhí)行
typeof resolveFn !== 'function' ? resolveFn = value => value : null
typeof rejectFn !== 'function' ? rejectFn = reason => {
throw new Error(reason instanceof Error? reason.message:reason);
} : null
// return一個新的promise
return new MyPromise((resolve, reject) => {
// 把resolveFn重新包裝一下,再push進resolve執(zhí)行隊列,這是為了能夠獲取回調(diào)的返回值進行分類討論
const fulfilledFn = value => {
try {
// 執(zhí)行第一個(當前的)Promise的成功回調(diào),并獲取返回值
let x = resolveFn(value)
// 分類討論返回值,如果是Promise,那么等待Promise狀態(tài)變更,否則直接resolve
x instanceof MyPromise ? x.then(resolve, reject) : resolve(x)
} catch (error) {
reject(error)
}
}
// reject同理
const rejectedFn = error => {
try {
let x = rejectFn(error)
x instanceof MyPromise ? x.then(resolve, reject) : resolve(x)
} catch (error) {
reject(error)
}
}
switch (this._status) {
// 當狀態(tài)為pending時,把then回調(diào)push進resolve/reject執(zhí)行隊列,等待執(zhí)行
case PENDING:
this._resolveQueue.push(fulfilledFn)
this._rejectQueue.push(rejectedFn)
break;
// 當狀態(tài)已經(jīng)變?yōu)閞esolve/reject時,直接執(zhí)行then回調(diào)
case FULFILLED:
fulfilledFn(this._value) // this._value是上一個then回調(diào)return的值(見完整版代碼)
break;
case REJECTED:
rejectedFn(this._value)
break;
}
})
}
}
注:最后的實現(xiàn)和上面兩個相比加了定時器的處理,注意仔細查看,是為了處理promise的返回順序。
promise的其他方法實現(xiàn)
promise.prototype.catch()
catch()方法返回一個promise 并且處理拒絕的情況
//catch方法其實就是執(zhí)行一下then的第二個回調(diào)
catch(rejectFn) {
return this.then(undefined, rejectFn)
}
promise.prototype.finally()
finally()方法返回一個promise 在promise結束后 ,無論結果是fulfilled或者 rejected 都會執(zhí)行指定的回調(diào)函數(shù), 在finally之后 花可以繼續(xù)使用then,
//finally方法
finally(callback) {
return this.then(
value => MyPromise.resolve(callback()).then(() => value),
// MyPromise.resolve執(zhí)行回調(diào),并在then中return結果傳遞給后面的Promise
reason => MyPromise.resolve(callback()).then(() => { throw reason })
// reject同理
)
}
promise.resolve()
promise.resolve(value)方法返回一個已給定值解析后的promise對象 如果該值為promise 返回這個promise;如果這個值為then方法, 返回的promise會跟隨這個方法的對象,采用它的最終狀態(tài),否則返回的promise將以此值完成。
//靜態(tài)的resolve方法
static resolve(value) {
if(value instanceof MyPromise) return value // 根據(jù)規(guī)范, 如果參數(shù)是Promise實例, 直接return這個實例
return new MyPromise(resolve => resolve(value))
}
Promise.reject()
Promise.reject()
方法返回一個帶有拒絕原因的Promise對象。
//靜態(tài)的reject方法
static reject(reason) {
return new MyPromise((resolve, reject) => reject(reason))
}
復制代碼
Promise.all()
Promise.all(iterable)
方法返回一個 Promise 實例,此實例在 iterable 參數(shù)內(nèi)所有的 promise 都“完成(resolved)”或參數(shù)中不包含 promise 時回調(diào)完成(resolve);如果參數(shù)中 promise 有一個失?。╮ejected),此實例回調(diào)失?。╮eject),失敗原因的是第一個失敗 promise 的結果。
//靜態(tài)的all方法
static all(promiseArr) {
let index = 0
let result = []
return new MyPromise((resolve, reject) => {
promiseArr.forEach((p, i) => {
//Promise.resolve(p)用于處理傳入值不為Promise的情況
MyPromise.resolve(p).then(
val => {
index++
result[i] = val
//所有then執(zhí)行后, resolve結果
if(index === promiseArr.length) {
resolve(result)
}
},
err => {
//有一個Promise被reject時,MyPromise的狀態(tài)變?yōu)閞eject
reject(err)
}
)
})
})
}
復制代碼
Promise.race()
Promise.race(iterable)
方法返回一個 promise,一旦迭代器中的某個promise解決或拒絕,返回的 promise就會解決或拒絕。
static race(promiseArr) {
return new MyPromise((resolve, reject) => {
//同時執(zhí)行Promise,如果有一個Promise的狀態(tài)發(fā)生改變,就變更新MyPromise的狀態(tài)
for (let p of promiseArr) {
MyPromise.resolve(p).then( //Promise.resolve(p)用于處理傳入值不為Promise的情況
value => {
resolve(value) //注意這個resolve是上邊new MyPromise的
},
err => {
reject(err)
}
)
}
})
}
完整promise代碼
//Promise/A+規(guī)定的三種狀態(tài)
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class MyPromise {
// 構造方法接收一個回調(diào)
constructor(executor) {
this._status = PENDING // Promise狀態(tài)
this._value = undefined // 儲存then回調(diào)return的值
this._resolveQueue = [] // 成功隊列, resolve時觸發(fā)
this._rejectQueue = [] // 失敗隊列, reject時觸發(fā)
// 由于resolve/reject是在executor內(nèi)部被調(diào)用, 因此需要使用箭頭函數(shù)固定this指向, 否則找不到this._resolveQueue
let _resolve = (val) => {
//把resolve執(zhí)行回調(diào)的操作封裝成一個函數(shù),放進setTimeout里,以兼容executor是同步代碼的情況
const run = () => {
if(this._status !== PENDING) return // 對應規(guī)范中的"狀態(tài)只能由pending到fulfilled或rejected"
this._status = FULFILLED // 變更狀態(tài)
this._value = val // 儲存當前value
// 這里之所以使用一個隊列來儲存回調(diào),是為了實現(xiàn)規(guī)范要求的 "then 方法可以被同一個 promise 調(diào)用多次"
// 如果使用一個變量而非隊列來儲存回調(diào),那么即使多次p1.then()也只會執(zhí)行一次回調(diào)
while(this._resolveQueue.length) {
const callback = this._resolveQueue.shift()
callback(val)
}
}
setTimeout(run)
}
// 實現(xiàn)同resolve
let _reject = (val) => {
const run = () => {
if(this._status !== PENDING) return // 對應規(guī)范中的"狀態(tài)只能由pending到fulfilled或rejected"
this._status = REJECTED // 變更狀態(tài)
this._value = val // 儲存當前value
while(this._rejectQueue.length) {
const callback = this._rejectQueue.shift()
callback(val)
}
}
setTimeout(run)
}
// new Promise()時立即執(zhí)行executor,并傳入resolve和reject
executor(_resolve, _reject)
}
// then方法,接收一個成功的回調(diào)和一個失敗的回調(diào)
then(resolveFn, rejectFn) {
// 根據(jù)規(guī)范,如果then的參數(shù)不是function,則我們需要忽略它, 讓鏈式調(diào)用繼續(xù)往下執(zhí)行
typeof resolveFn !== 'function' ? resolveFn = value => value : null
typeof rejectFn !== 'function' ? rejectFn = reason => {
throw new Error(reason instanceof Error? reason.message:reason);
} : null
// return一個新的promise
return new MyPromise((resolve, reject) => {
// 把resolveFn重新包裝一下,再push進resolve執(zhí)行隊列,這是為了能夠獲取回調(diào)的返回值進行分類討論
const fulfilledFn = value => {
try {
// 執(zhí)行第一個(當前的)Promise的成功回調(diào),并獲取返回值
let x = resolveFn(value)
// 分類討論返回值,如果是Promise,那么等待Promise狀態(tài)變更,否則直接resolve
x instanceof MyPromise ? x.then(resolve, reject) : resolve(x)
} catch (error) {
reject(error)
}
}
// reject同理
const rejectedFn = error => {
try {
let x = rejectFn(error)
x instanceof MyPromise ? x.then(resolve, reject) : resolve(x)
} catch (error) {
reject(error)
}
}
switch (this._status) {
// 當狀態(tài)為pending時,把then回調(diào)push進resolve/reject執(zhí)行隊列,等待執(zhí)行
case PENDING:
this._resolveQueue.push(fulfilledFn)
this._rejectQueue.push(rejectedFn)
break;
// 當狀態(tài)已經(jīng)變?yōu)閞esolve/reject時,直接執(zhí)行then回調(diào)
case FULFILLED:
fulfilledFn(this._value) // this._value是上一個then回調(diào)return的值(見完整版代碼)
break;
case REJECTED:
rejectedFn(this._value)
break;
}
})
}
//catch方法其實就是執(zhí)行一下then的第二個回調(diào)
catch(rejectFn) {
return this.then(undefined, rejectFn)
}
//finally方法
finally(callback) {
return this.then(
value => MyPromise.resolve(callback()).then(() => value), //執(zhí)行回調(diào),并returnvalue傳遞給后面的then
reason => MyPromise.resolve(callback()).then(() => { throw reason }) //reject同理
)
}
//靜態(tài)的resolve方法
static resolve(value) {
if(value instanceof MyPromise) return value //根據(jù)規(guī)范, 如果參數(shù)是Promise實例, 直接return這個實例
return new MyPromise(resolve => resolve(value))
}
//靜態(tài)的reject方法
static reject(reason) {
return new MyPromise((resolve, reject) => reject(reason))
}
//靜態(tài)的all方法
static all(promiseArr) {
let index = 0
let result = []
return new MyPromise((resolve, reject) => {
promiseArr.forEach((p, i) => {
//Promise.resolve(p)用于處理傳入值不為Promise的情況
MyPromise.resolve(p).then(
val => {
index++
result[i] = val
if(index === promiseArr.length) {
resolve(result)
}
},
err => {
reject(err)
}
)
})
})
}
//靜態(tài)的race方法
static race(promiseArr) {
return new MyPromise((resolve, reject) => {
//同時執(zhí)行Promise,如果有一個Promise的狀態(tài)發(fā)生改變,就變更新MyPromise的狀態(tài)
for (let p of promiseArr) {
MyPromise.resolve(p).then( //Promise.resolve(p)用于處理傳入值不為Promise的情況
value => {
resolve(value) //注意這個resolve是上邊new MyPromise的
},
err => {
reject(err)
}
)
}
})
}
}
二 Generator
ES6 新引入Generator函數(shù),也可以說是一種數(shù)據(jù)類型;最大的特點就是暫停執(zhí)行。
關鍵詞是function后面有一個*
,
Generato原理:
1、 Generator可以通過yield關鍵字,把函數(shù)的執(zhí)行流掛起,掛起的函數(shù)不會馬上就執(zhí)行,而是返回一個指向內(nèi)部狀態(tài)的指針對象;
2、 這個對象是一個遍歷器(iterator)對象,iterator對象上有一個next()方法。
3、通過調(diào)用next()方法切換到下一個狀態(tài),移動內(nèi)部指針,使得指針指向下一個狀態(tài)。
4、next()同時會返回一個對象,表示當前階段的信息- 其中 value 屬性是 yield 語句后面表達式的值,表示當前階段的值;done 屬性表示 Generator 函數(shù)是否執(zhí)行完畢,即是否還有下一個階段
5、 返回遍歷器對象有個 throw
方法可以拋出錯誤,拋出的錯誤可以被函數(shù)體內(nèi)的 try/catch 代碼塊捕獲
注:yield 只能在 Generator 中使用,在其他地方使用會報錯
function* myGenerator() {
yield '1'
yield '2'
return '3'
}
const gen = myGenerator(); // 獲取迭代器
gen.next() //{value: "1", done: false}
gen.next() //{value: "2", done: false}
gen.next() //{value: "3", done: true}
也可以通過給next()
傳參, 讓yield具有返回值
function* myGenerator() {
console.log(yield '1') //test1
console.log(yield '2') //test2
console.log(yield '3') //test3
}
// 獲取迭代器
const gen = myGenerator();
gen.next()
gen.next('test1')
gen.next('test2')
gen.next('test3')
那么具體Generator是怎么執(zhí)行的呢?
這里參考:鏈接
中方法,轉(zhuǎn)化為es5 方法后代碼如下:
"use strict";
var _marked =
/*#__PURE__*/
// mark()是內(nèi)置的一個方法 為生成器綁定了一系列的原型
regeneratorRuntime.mark(foo);
function foo() {
// wrap() return一個方法
return regeneratorRuntime.wrap(function foo$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
_context.next = 2;
return 'result1';
case 2:
_context.next = 4;
return 'result2';
case 4:
_context.next = 6;
return 'result3';
case 6:
case "end":
return _context.stop();
}
}
}, _marked);
}
var gen = foo();
console.log(gen.next().value);
console.log(gen.next().value);
console.log(gen.next().value);
分析一下上面的代碼會發(fā)現(xiàn),Generator的實現(xiàn)核心在于用一個context對象儲存上下文,
每一次yield的時候;函數(shù)并沒有被真正掛起,在執(zhí)行過程中都傳入一遍生成器函數(shù),然后用context存儲上下文,每次執(zhí)行生成器函數(shù)的時候,都可以從上一個執(zhí)行結果開始執(zhí)行,看上去就像函數(shù)被掛起一樣。
調(diào)用流程如下:
-
我們定義的
function*
生成器函數(shù)被轉(zhuǎn)化為以上代碼 -
轉(zhuǎn)化后的代碼分為三大塊:
-
gen$(_context)
由yield分割生成器函數(shù)代碼而來 -
context對象
用于儲存函數(shù)執(zhí)行上下文 -
invoke()方法
定義next(),用于執(zhí)行gen$(_context)來跳到下一步
-
-
當我們調(diào)用
g.next()
,就相當于調(diào)用invoke()方法
,執(zhí)行gen$(_context)
,進入switch語句,switch根據(jù)context的標識,執(zhí)行對應的case塊,return對應結果 -
當生成器函數(shù)運行到末尾(沒有下一個yield或已經(jīng)return),switch匹配不到對應代碼塊,就會return空值,這時
g.next()
返回{value: undefined, done: true}
二 async/await
async/await實現(xiàn)
首先 async/await實際上是對Generator的封裝,是一個語法糖 我們對Generator不熟悉的原因是出現(xiàn)不久后就被更方便的async/await所取代了
我們在Generator章節(jié)分析到,Generator中yiled的用法和async/await的用法有很多相同的地方,都提供了暫停執(zhí)行的功能,但是具體的不同有三點:
- async/await自帶執(zhí)行器 不需要手動next()就可以執(zhí)行下一步
- async函數(shù)返回的值直接是一個promise對象,Genreator返回的是生成器對象
- await能夠返回Promise的resolve/reject的值
所以我們在實現(xiàn)async/await時 也是在Generator上增加上面三點功能
自動執(zhí)行:
在Generator中 手動執(zhí)行如下代碼:
function* myGenerator() {
yield Promise.resolve(1);
yield Promise.resolve(2);
yield Promise.resolve(3);
}
// 手動執(zhí)行迭代器
const gen = myGenerator()
gen.next().value.then(val => {
console.log(val)
gen.next().value.then(val => {
console.log(val)
gen.next().value.then(val => {
console.log(val)
})
})
})
//輸出1 2 3
這里面發(fā)現(xiàn),要實現(xiàn)手動執(zhí)行就得使用尾調(diào)用的方式嵌套調(diào)用then方法;所以我們要實現(xiàn)生成器能自動往下執(zhí)行,并且yield能放回resolve的值,
注意:async/await是關鍵詞 不能重寫,下面使用函數(shù)來模擬
function run(gen) {
var g = gen()
//由于每次gen()獲取到的都是最新的迭代器,因此獲取迭代器操作要放在_next()之前,否則會進入死循環(huán)
function _next(val) {
//封裝一個方法, 遞歸執(zhí)行g.next()
var res = g.next(val)
//獲取迭代器對象,并返回resolve的值
if(res.done) return res.value
//遞歸終止條件
res.value.then(val => {
//Promise的then方法是實現(xiàn)自動迭代的前提
_next(val)
//等待Promise完成就自動執(zhí)行下一個next,并傳入resolve的值
})
}
_next()
//第一次執(zhí)行
}
在上面的代碼中 我們通過判斷條件 來實現(xiàn)循環(huán)調(diào)用,把下一步的操作封裝成——next(),每次Promise.then()的時候都執(zhí)行——next(),實現(xiàn)循環(huán)調(diào)用的效果, 這樣可以簡化上一個代碼塊如下:
function* myGenerator() {
console.log(yield Promise.resolve(1)) //1
console.log(yield Promise.resolve(2)) //2
console.log(yield Promise.resolve(3)) //3
}
run(myGenerator)
返回Promise和異常處理
這兩個也是上面提到的特點;
返回promise:這里我們可以把上述代碼中的(gen().next.value)
都用Promise.resolve()轉(zhuǎn)化一遍
異常處理:如果promise執(zhí)行失敗,導致后續(xù)的執(zhí)行中斷,采用Generator.prototype.throw()
來拋出錯誤,這樣能被外層的try-catch捕獲到
所以這里我們改造一下上面一段代碼:
function run(gen) {
//把返回值包裝成promise
return new Promise((resolve, reject) => {
var g = gen()
function _next(val) {
//錯誤處理
try {
var res = g.next(val)
} catch (err) {
return reject(err);
}
if (res.done) {
return resolve(res.value);
}
//res.value包裝為promise,以兼容yield后面跟基本類型的情況
Promise.resolve(res.value).then(
val => {
_next(val);
},
err => {
//拋出錯誤
g.throw(err)
});
}
_next();
});
}
測試:
function* myGenerator() {
try {
console.log(yield Promise.resolve(1))
console.log(yield 2) //2
console.log(yield Promise.reject('error'))
} catch (error) {
console.log(error)
}
}
const result = run(myGenerator) //result是一個Promise
//輸出 1 2 error
寫到最后發(fā)現(xiàn),主要去講他們怎么實現(xiàn),都8000多字,尷尬,很多概念還沒講。深入學習之后發(fā)現(xiàn)每一個點都能寫很多,所以只是寫一些主要知識點,在實現(xiàn)后也能看出他們的原理,以及概念在運行過程中是怎么用的。文章是在學習過程中寫的,有很多概念可能有錯,如果有大佬能指出不勝感激!!
參考鏈接:
# 9k字 | Promise/async/Generator實現(xiàn)原理解析 文章來源:http://www.zghlxwxcb.cn/news/detail-404304.html
# BAT前端經(jīng)典面試問題:史上最最最詳細的手寫Promise教程文章來源地址http://www.zghlxwxcb.cn/news/detail-404304.html
到了這里,關于深入學習JavaScript系列(七)——Promise async/await generator的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!