1.高級 WEB 面試會讓你手寫一個Promise,Generator 的 PolyFill(一段代碼);
2.在寫以前咱們簡單回顧下他們的做用;
3.手寫模塊見PolyFill.vue
源碼地址請戳,原創碼字不易,歡迎 star react
若是以爲看文章太囉嗦,能夠直接 git clone ,直接看代碼git
Promise 你們應該都用過,ajax 庫就是利用 Promise封裝的;
做用主要是解決地獄回調問題.github
new Promise((resolve,reject)=>{ resolve('這是第一個 resolve 值') }).then((data)=>{ console.log(data) //會打印'這是第一個 resolve 值' }).catch(()=>{ }) new Promise((resolve,reject)=>{ reject('這是第一個 reject 值') }).then((data)=>{ console.log(data) }).catch((data)=>{ console.log(data) //會打印'這是第一個 reject 值' })
Promise.resolve('這是第二個 resolve 值').then((data)=>{ console.log(data) // 會打印'這是第二個 resolve 值' }) Promise.reject('這是第二個 reject 值').then((data)=>{ console.log(data) }).catch(data=>{ console.log(data) //這是第二個 reject 值 })
表示多個 Promise 都進入到 FulFilled 或者 Rejected 就會執行面試
const pOne = new Promise((resolve, reject) => { resolve(1); }); const pTwo = new Promise((resolve, reject) => { resolve(2); }); const pThree = new Promise((resolve, reject) => { resolve(3); }); Promise.all([pOne, pTwo, pThree]).then(data => { console.log(data); // [1, 2, 3] 正常執行完畢會執行這個,結果順序和promise實例數組順序是一致的 }, err => { console.log(err); // 任意一個報錯信息 });
表示多個 Promise 只有一個進入到 FulFilled 或者 Rejected 就會執行ajax
const pOne = new Promise((resolve, reject) => { resolve(1); }); const pTwo = new Promise((resolve, reject) => { resolve(2); }); const pThree = new Promise((resolve, reject) => { // resolve(3); }); Promise.race([pOne, pTwo, pThree]).then(data => { console.log(data); // 1 只要碰到FulFilled 或者 Rejected就會中止執行 }, err => { console.log(err); // 任意一個報錯信息 });
1.Promise接受一個函數handle做爲參數,handle包括resolve和reject兩個是函數的參數
2.Promise 至關於一個狀態機,有三種狀態:pending,fulfilled,reject,初始狀態爲 pending
3.調用 resolve,狀態由pending => fulfilled
4.調用reject,會由pending => rejected
5.改變以後不會變化數組
1.接受兩個參數,onFulfilled和onRejected可選的函數 promise
2.不是函數必須被忽略 koa
3.onFullfilled:
A.當 promise 狀態變爲成功時必須被調用,其第一個參數爲 promise 成功狀態傳入的值( resolve 執行時傳入的值;
B.在 promise 狀態改變前其不可被調用
C.其調用次數不可超過一次 異步
4.onRejected:做用和onFullfilled相似,只不過是promise失敗調用
5.then方法能夠鏈式調用
A.每次返回一個新的Promise
B.執行規則和錯誤捕獲:then的返回值若是是非Promise直接做爲下一個新Promise參數,若是是Promise會等Promise執行
// 返回非Promise let promise1 = new Promise((resolve, reject) => { setTimeout(() => { resolve() }, 1000) }) promise2 = promise1.then(res => { // 返回一個普通值 return '這裏返回一個普通值' }) promise2.then(res => { console.log(res) //1秒後打印出:這裏返回一個普通值 }) // 返回Promise let promise1 = new Promise((resolve, reject) => { setTimeout(() => { resolve() }, 1000) }) promise2 = promise1.then(res => { // 返回一個Promise對象 return new Promise((resolve, reject) => { setTimeout(() => { resolve('這裏返回一個Promise') }, 2000) }) }) promise2.then(res => { console.log(res) //3秒後打印出:這裏返回一個Promise })
C. onFulfilled 或者onRejected 拋出一個異常 e ,則 promise2 必須變爲失敗(Rejected),並返回失敗的值 e
let promise1 = new Promise((resolve, reject) => { setTimeout(() => { resolve('success') }, 1000) }) promise2 = promise1.then(res => { throw new Error('這裏拋出一個異常e') }) promise2.then(res => { console.log(res) }, err => { console.log(err) //1秒後打印出:這裏拋出一個異常e })
D.onFulfilled 不是函數且 promise1 狀態爲成功(Fulfilled), promise2 必須變爲成功(Fulfilled)並返回 promise1 成功的值
let promise1 = new Promise((resolve, reject) => { setTimeout(() => { resolve('success') }, 1000) }) promise2 = promise1.then('這裏的onFulfilled原本是一個函數,但如今不是') promise2.then(res => { console.log(res) // 1秒後打印出:success }, err => { console.log(err) })
E.onRejected 不是函數且 promise1 狀態爲失敗(Rejected),promise2必須變爲失敗(Rejected) 並返回 promise1 失敗的值
let promise1 = new Promise((resolve, reject) => { setTimeout(() => { reject('fail') }, 1000) }) promise2 = promise1.then(res => res, '這裏的onRejected原本是一個函數,但如今不是') promise2.then(res => { console.log(res) }, err => { console.log(err) // 1秒後打印出:fail })
F.resolve和reject結束一個Promise的調用
G.catch方法,捕獲異常
其實複雜的是在then的異常處理上,不過不用急,邊看邊理解
1.靜態resolve方法
參照1.3.1用法2
2.靜態reject方法
參照1.3.1用法2
3.靜態all方法
參照1.3.1用法3
4.靜態race方法
參照1.3.1用法4
5.自定義done方法
Promise 對象的回調鏈,無論以then方法或catch方法結尾,要是最後一個方法拋出錯誤,都有可能沒法捕捉到(由於 Promise內部的錯誤不會冒泡到全局)
所以,咱們能夠提供一個done方法,老是處於回調鏈的尾端,保證拋出任何可能出現的錯誤
Promise.prototype.done = function (onFulfilled, onRejected) { this .then(onFulfilled, onRejected) .catch(function (reason) { // 拋出一個全局錯誤 setTimeout(() => { throw reason }, 0) }) }
6.自定義finally方法
finally方法用於指定無論Promise對象最後狀態如何,都會執行的操做
它與done方法的最大區別,它接受一個普通的回調函數做爲參數,該函數無論怎樣都必須執行
Promise.prototype.finally = function (callback) { let P = this.constructor return this.then( value => P.resolve(callback()).then(() => value), reason => P.resolve(callback()).then(() => { throw reason }) ) }
class MyPromise { constructor (handle) { // 判斷handle函數與否 if (typeof handle!=='function') { throw new Error('MyPromise must accept a function as a parameter') } // 添加狀態 this._status = 'PENDING' // 添加狀態 this._value = undefined // 執行handle try { handle(this._resolve.bind(this), this._reject.bind(this)) } catch (err) { this._reject(err) } } // 添加resovle時執行的函數 _resolve (val) { if (this._status !== 'PENDING') return this._status = 'FULFILLED' this._value = val } // 添加reject時執行的函數 _reject (err) { if (this._status !== 'PENDING') return this._status = 'REJECTED' this._value = err } }
回顧一下,初級版實現了1,2,3這三點功能,怎麼樣仍是so-easy吧!
1.因爲 then 方法支持屢次調用,咱們能夠維護兩個數組,將每次 then 方法註冊時的回調函數添加到數組中,等待執行
在初級的基礎上加入成功回調函數隊列和失敗回調隊列和then方法
this._fulfilledQueues = [] this._rejectedQueues = []
2.then方法
將
then (onFulfilled, onRejected) { const { _value, _status } = this switch (_status) { // 當狀態爲pending時,將then方法回調函數加入執行隊列等待執行 case 'PENDING': this._fulfilledQueues.push(onFulfilled) this._rejectedQueues.push(onRejected) break // 當狀態已經改變時,當即執行對應的回調函數 case 'FULFILLED': onFulfilled(_value) break case 'REJECTED': onRejected(_value) break } // 返回一個新的Promise對象 return new MyPromise((onFulfilledNext, onRejectedNext) => { }) }
3.then方法規則完善
// 添加then方法 then (onFulfilled, onRejected) { const { _value, _status } = this // 返回一個新的Promise對象 return new MyPromise((onFulfilledNext, onRejectedNext) => { // 封裝一個成功時執行的函數 let fulfilled = value => { try { if (typeof onFulfilled!=='function') { onFulfilledNext(value) } else { let res = onFulfilled(value); if (res instanceof MyPromise) { // 若是當前回調函數返回MyPromise對象,必須等待其狀態改變後在執行下一個回調 res.then(onFulfilledNext, onRejectedNext) } else { //不然會將返回結果直接做爲參數,傳入下一個then的回調函數,並當即執行下一個then的回調函數 onFulfilledNext(res) } } } catch (err) { // 若是函數執行出錯,新的Promise對象的狀態爲失敗 onRejectedNext(err) } } // 封裝一個失敗時執行的函數 let rejected = error => { try { if (typeof onRejected!=='function') { onRejectedNext(error) } else { let res = onRejected(error); if (res instanceof MyPromise) { // 若是當前回調函數返回MyPromise對象,必須等待其狀態改變後在執行下一個回調 res.then(onFulfilledNext, onRejectedNext) } else { //不然會將返回結果直接做爲參數,傳入下一個then的回調函數,並當即執行下一個then的回調函數 onFulfilledNext(res) } } } catch (err) { // 若是函數執行出錯,新的Promise對象的狀態爲失敗 onRejectedNext(err) } } switch (_status) { // 當狀態爲pending時,將then方法回調函數加入執行隊列等待執行 case 'PENDING': this._fulfilledQueues.push(fulfilled) this._rejectedQueues.push(rejected) break // 當狀態已經改變時,當即執行對應的回調函數 case 'FULFILLED': fulfilled(_value) break case 'REJECTED': rejected(_value) break } }) }
4.當 resolve 或 reject 方法執行時,咱們依次提取成功或失敗任務隊列當中的函數開始執行,並清空隊列,從而實現 then 方法的屢次調用
// 添加resovle時執行的函數 _resolve (val) { if (this._status !== PENDING) return // 依次執行成功隊列中的函數,並清空隊列 const run = () => { this._status = FULFILLED this._value = val let cb; while (cb = this._fulfilledQueues.shift()) { cb(val) } } // 爲了支持同步的Promise,這裏採用異步調用 setTimeout(() => run(), 0) } // 添加reject時執行的函數 _reject (err) { if (this._status !== PENDING) return // 依次執行失敗隊列中的函數,並清空隊列 const run = () => { this._status = REJECTED this._value = err let cb; while (cb = this._rejectedQueues.shift()) { cb(err) } } // 爲了支持同步的Promise,這裏採用異步調用 setTimeout(run, 0) }
5.當 resolve 方法傳入的參數爲一個 Promise 對象時,則該 Promise 對象狀態決定當前 Promise 對象的狀態
// 添加resovle時執行的函數 _resolve (val) { const run = () => { if (this._status !== PENDING) return this._status = FULFILLED // 依次執行成功隊列中的函數,並清空隊列 const runFulfilled = (value) => { let cb; while (cb = this._fulfilledQueues.shift()) { cb(value) } } // 依次執行失敗隊列中的函數,並清空隊列 const runRejected = (error) => { let cb; while (cb = this._rejectedQueues.shift()) { cb(error) } } /* 若是resolve的參數爲Promise對象,則必須等待該Promise對象狀態改變後, 當前Promsie的狀態纔會改變,且狀態取決於參數Promsie對象的狀態 */ if (val instanceof MyPromise) { val.then(value => { this._value = value runFulfilled(value) }, err => { this._value = err runRejected(err) }) } else { this._value = val runFulfilled(val) } } // 爲了支持同步的Promise,這裏採用異步調用 setTimeout(run, 0) }
6.catch方法
// 添加catch方法 catch (onRejected) { return this.then(undefined, onRejected) }
1.靜態 resolve 方法
// 添加靜態resolve方法 static resolve (value) { // 若是參數是MyPromise實例,直接返回這個實例 if (value instanceof MyPromise) return value return new MyPromise(resolve => resolve(value)) }
2.靜態 reject 方法
// 添加靜態reject方法 static reject (value) { return new MyPromise((resolve ,reject) => reject(value)) }
3.靜態 all 方法
// 添加靜態all方法 static all (list) { return new MyPromise((resolve, reject) => { /** * 返回值的集合 */ let values = [] let count = 0 for (let [i, p] of list.entries()) { // 數組參數若是不是MyPromise實例,先調用MyPromise.resolve this.resolve(p).then(res => { values[i] = res count++ // 全部狀態都變成fulfilled時返回的MyPromise狀態就變成fulfilled if (count === list.length) resolve(values) }, err => { // 有一個被rejected時返回的MyPromise狀態就變成rejected reject(err) }) } }) }
4.靜態 race 方法
// 添加靜態race方法 static race (list) { return new MyPromise((resolve, reject) => { for (let p of list) { // 只要有一個實例率先改變狀態,新的MyPromise的狀態就跟着改變 this.resolve(p).then(res => { resolve(res) }, err => { reject(err) }) } }) }
5.done方法
做用:無論以then方法或catch方法結尾,要是最後一個方法拋出錯誤,都有可能沒法捕捉到(由於 Promise 內部的錯誤不會冒泡到全局);處於回調鏈的尾端,保證拋出任何可能出現的錯誤
目前 Promise 上尚未 done,咱們能夠自定義一個
Promise.prototype.done = function (onFulfilled, onRejected) { console.log('done') this.then(onFulfilled, onRejected) .catch((reason)=> { // 拋出一個全局錯誤 setTimeout(() => { throw reason }, 0) }) } Promise.resolve('這是靜態方法的第一個 resolve 值').then(()=>{ return '這是靜態方法的第二個 resolve 值' }).then(()=>{ throw('這是靜態方法的第一個 reject 值') return '這是靜態方法的第二個 resolve 值' }).done()
6.finally方法
做用:無論 Promise 對象最後狀態如何,都會執行的操做
與done方法的最大區別,它接受一個普通的回調函數做爲參數,該函數無論怎樣都必須執行
finally (cb) { return this.then( value => MyPromise.resolve(cb()).then(() => value), reason => MyPromise.resolve(cb()).then(() => { throw reason }) ); };
7.完整代碼
請戳,源碼地址
歡迎 star!
1.Generator能夠理解爲一個狀態機,內部封裝了不少狀態,同時返回一個迭代器Iterator對象;
2.迭代器Iterator對象:定義標準方式產生一個有限或無限序列值,迭代器有next()對象;
3.屢次返回能夠被 next屢次調用,最大特色是能夠控制執行順序;
2.是一種特殊的函數
function* gen(x){ const y = yield x + 6; return y; } // yield 若是用在另一個表達式中,要放在()裏面 // 像上面若是是在=右邊就不用加() function* genOne(x){ const y = `這是第一個 yield 執行:${yield x + 1}`; return y; }
整個 Generator 函數就是一個封裝的異步任務,或者說是異步任務的容器。異步操做須要暫停的地方,都用 yield 語句註明
1.普通執行
const g = gen(1); //執行 Generator 會返回一個Object,而不是像普通函數返回return 後面的值 g.next() // { value: 7, done: false } //調用指針的 next 方法,會從函數的頭部或上一次停下來的地方開始執行,直到遇到下一個 yield 表達式或return語句暫停,也就是執行yield 這一行 // 執行完成會返回一個 Object, // value 就是執行 yield 後面的值,done 表示函數是否執行完畢 g.next() // { value: undefined, done: true } // 由於最後一行 return y 被執行完成,因此done 爲 true
2.next 方法傳參數
const g = gen(1); g.next() // { value: 7, done: false } g.next(2) // { value: 2, done: true } // next 的參數是做爲上個階段異步任務的返回結果 3.嵌套執行 必須用到yield*關鍵字
function* genTwo(x){
yield* gen(1)
yield* genOne(1)
const y = 這是第 二個 yield 執行:${yield x + 2}
;
return y;
}
const iterator=genTwo(1)
// 由於 Generator 函數運行時生成的是一個 Iterator 對象,因此能夠直接使用 for...of 循環遍歷
for(let value of iterator) {
console.log(value)
}
相同點:
1.都能返回語句後面的那個表達式的值
2.均可以暫停函數執行
區別:
一個函數能夠有多個 yield,可是隻能有一個 return
yield 有位置記憶功能,return 沒有
拋出錯誤,能夠被try...catch...捕捉到
g.throw('這是拋出的一個錯誤'); // 這是拋出的一個錯誤
// 要求:函數valOne,valTwo,valThree 以此執行 function* someTask(){ try{ const valOne=yield 1 const valTwo=yield 2 const valThree=yield 3 }catch(e){ } } scheduler(someTask()); function scheduler(task) { const taskObj = task.next(task.value); console.log(taskObj) // 若是Generator函數未結束,就繼續調用 if (!taskObj.done) { task.value = taskObj.value scheduler(task); } }
原理圖
實現一個迭代器(Iterator)
// 源碼實現 function createIterator(items) { var i = 0 return { next: function() { var done = (i >= items.length) var value = !done ? items[i++] : undefined return { done: done, value: value } } } } // 應用 const iterator = createIterator([1, 2, 3]) console.log(iterator.next()) // {value: 1, done: false} console.log(iterator.next()) // {value: 2, done: false} console.log(iterator.next()) // {value: 3, done: false} console.log(iterator.next()) // {value: undefined, done: true}
實現可迭代(Iterable)
1.能夠經過 for...of...遍歷的對象,即原型鏈上有Symbol.iterator屬性;
2.Symbol.iterator:返回一個對象的無參函數,被返回對象符合迭代器協議;
3.for...of接受一個可迭代對象(Iterable),或者能強制轉換/包裝成一個可迭代對象的值(如’abc’),遍歷時,for...of會獲取可迭代對象的'Symbol.iterator',對該迭代器逐次調用next(),直到迭代器返回對象的done屬性爲true時,遍歷結束,不對該value處理;
const a = ['a', 'b', 'c', 'd', 'e'] for (let val of a) { console.log(val) } // 'a' 'b' 'c' 'd' 'e' // 等價於 const a = ["a", "b", "c", "d", "e"] for (let val, ret, it = a[Symbol.iterator](); (ret = it.next()) && !ret.done; ) { let = ret.value console.log(val) } // "a" "b" "c" "d" "e"
4.yield* 可返回一個 Iterable對象;
5.源碼改造
function createIterator(items) { let i = 0 return { next: function () { let done = (i >= items.length) let value = !done ? items[i++] : undefined return { done: done, value: value } } [Symbol.iterator]: function () { return this } } } let iterator = createIterator([1, 2, 3]) ...iterator // 1, 2, 3
1.for...of...接收可迭代對象,能強制轉換或包裝可迭代對象的值;
2.遍歷時,for...of會獲取可迭代對象的'Symbol.iterator',對該迭代器逐次調用next(),直到迭代器返回對象的done屬性爲true時,遍歷結束,不對該value處理;
3.因此能夠利用 for...of...封裝到原型鏈上.
Object.prototype[Symbol.iterator] = function* () { for (const key in this) { if (this.hasOwnProperty(key)) { yield [key, this[key]] } } }
1.async 函數返回的是一個 Promise 對象
在函數中 return 一個直接量,async 會把這個直接量經過 Promise.resolve() 封裝成 Promise 對象
async function testAsync() { return "hello async"; } const result = testAsync(); console.log(result); //Promise 對象
2.async和then
async返回一個Promise,因此能夠經過then獲取值
testAsync().then(v => { console.log(v); // 輸出 hello async });
因此async裏面的函數會立刻執行,這個就相似Generator的‘*’
1.await後面能夠是Promise對象或其餘表達式
function getSomething() { return "something"; } async function testAsync() { return Promise.resolve("hello async"); } async function test() { const v1 = await getSomething(); const v2 = await testAsync(); console.log(v1, v2); //something 和 hello async } test();
2.await後面不是Promise對象,直接執行
3.await後面是Promise對象會阻塞後面的代碼,Promise 對象 resolve,而後獲得 resolve 的值,做爲 await 表達式的運算結果
4.因此這就是await必須用在async的緣由,async恰好返回一個Promise對象,能夠異步執行阻塞
1.主要是處理Promise的鏈式回調或函數的地獄回調
回到Generator中要求函數valOne,valTwo,valThree函數依次執行
function valOne(){} function valTwo(){} function valThree(){} async ()=>{ await valOne() await valTwo() await valThree() }
2.處理異常
try...catch...
或者await .catch()
1.async是內置執行器,Generator 函數的執行必須依靠執行器,無需手動執行next()
2.更廣的適用性。co模塊約定,yield命令後面只能是 Thunk 函數或 Promise 對象,而await後面能夠是任意表達式,都會返回一個Promise對象
// Thunk函數:是能將執行結果傳入回調函數,並將該回調函數返回的函數 function f(m) { return m * 2; } f(x + 5); // 等同於 var thunk = function () { return x + 5; }; function f(thunk) { return thunk() * 2; }
3.返回Promise,而Generator返回 Iterator
4.async 函數就是 Generator 函數的語法糖
async就至關於Generator的*,await至關於yield,用法有不少類似之處
實現執行器兩種方式:
回調函數(Thunk 函數)
Promise 對象
https://juejin.im/post/5dc28e...
async function fn(args) { // ... } // 等價於 function fn(args) { return spawn(function* () { // ... }); } function spawn(gen){ let g = gen(); function next(data){ let result = g.next(data); if (result.done) return result.value; result.value.then(function(data){ next(data); }); } next(); }
function spawn(genF) { //spawn函數就是自動執行器,跟簡單版的思路是同樣的,多了Promise和容錯處理 return new Promise(function(resolve, reject) { const gen = genF(); function step(nextF) { let next; try { next = nextF(); } catch(e) { return reject(e); } if(next.done) { return resolve(next.value); } Promise.resolve(next.value).then(function(v) { step(function() { return gen.next(v); }); }, function(e) { step(function() { return gen.throw(e); }); }); } step(function() { return gen.next(undefined); }); }); }
1.代碼對比:
場景:假定某個 DOM 元素上面,部署了一系列的動畫,前一個動畫結束,才能開始後一個。若是當中有一個動畫出錯,就再也不往下執行,返回上一個成功執行的動畫的返回值。
A.Promise
function chainAnimationsPromise(elem, animations) { // 變量ret用來保存上一個動畫的返回值 let ret = null; // 新建一個空的Promise let p = Promise.resolve(); // 使用then方法,添加全部動畫 for(let anim of animations) { p = p.then(function(val) { ret = val; return anim(elem); }); } // 返回一個部署了錯誤捕捉機制的Promise return p.catch(function(e) { /* 忽略錯誤,繼續執行 */ }).then(function() { return ret; }); }
B.Generator
function chainAnimationsGenerator(elem, animations) { return spawn(function*() { let ret = null; try { for(let anim of animations) { ret = yield anim(elem); } } catch(e) { /* 忽略錯誤,繼續執行 */ } return ret; }); }
C.async
async function chainAnimationsAsync(elem, animations) { let ret = null; try { for(let anim of animations) { ret = await anim(elem); } } catch(e) { /* 忽略錯誤,繼續執行 */ } return ret; }
對比能夠看出 async...await...代碼更優雅
async 和 await 是在 Generator 的基礎上封裝了自執行函數和一些特性;
具體對比見沒個 API 的 PolyFill
來道頭條的面試
console.log('script start') async function async1() { await async2() console.log('async1 end') } async function async2() { console.log('async2 end') } async1() setTimeout(function() { console.log('setTimeout') }, 0) new Promise(resolve => { console.log('Promise') resolve() }) .then(function() { console.log('promise1') }) .then(function() { console.log('promise2') }) console.log('script end') // 舊版 Chrome 打印 // script start => async2 end => Promise => script end => promise1 => promise2 => async1 end => setTimeout // 新版 Chrome 打印 // script start => async2 end => Promise => script end => async1 end => promise1 => promise2 => setTimeout // 這裏面其餘的順序除了async1 end , promise1 , promise2 這幾個順序有點爭議,其餘應該沒有什麼問題 // 新版是由於V8 團隊將最新的規範進行了修改,await變得更快了,這道題細節分析再也不贅述,上面原理都有講到
原創碼字不易,歡迎 star!