JS高級之手寫一個Promise,Generator,async和await【近1W字】

NO IMAGE

前言

1.高級 WEB 面試會讓你手寫一個Promise,Generator 的 PolyFill(一段代碼);
2.在寫之前我們簡單回顧下他們的作用;
3.手寫模塊見PolyFill.

源碼

源碼地址請戳,原創碼字不易,歡迎 star

如果覺得看文章太囉嗦,可以直接 git clone ,直接看代碼

1.Promise

1.1 作用

Promise 大家應該都用過,ajax 庫就是利用 Promise封裝的;
作用主要是解決地獄回調問題.

1.2 使用

1.2.1.方法一

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 值'
})

1.2.2.方法二(靜態方法)

Promise.resolve('這是第二個 resolve 值').then((data)=>{
console.log(data) // 會打印'這是第二個 resolve 值'
})
Promise.reject('這是第二個 reject 值').then((data)=>{
console.log(data)
}).catch(data=>{
console.log(data) //這是第二個 reject 值
})

1.2.3.方法三(多個 Promise並行執行異步操作)

表示多個 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); // 任意一個報錯信息
});

1.2.4.方法四(多箇中一個正常執行)

表示多個 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.race([pOne, pTwo, pThree]).then(data => { 
console.log(data); // 1 只要碰到FulFilled 或者 Rejected就會停止執行
}, err => {
console.log(err); // 任意一個報錯信息
});

1.3 作用分析

1.3.1 參數和狀態

1.Promise接受一個函數handle作為參數,handle包括resolve和reject兩個是函數的參數
2.Promise 相當於一個狀態機,有三種狀態:pending,fulfilled,reject,初始狀態為 pending
3.調用 resolve,狀態由pending => fulfilled
4.調用reject,會由pending => rejected
5.改變之後不會變化

1.3.2 then 方法

1.接受兩個參數,onFulfilled和onRejected可選的函數

2.不是函數必須被忽略

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.3.3 方法

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
})
)
}

1.4 PolyFill

1.4.1 初級版

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.4.2 中級版

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.4.3 高級版

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!

2.Generator

2.1 定義

1.Generator可以理解為一個狀態機,內部封裝了很多狀態,同時返回一個迭代器Iterator對象;
2.迭代器Iterator對象:定義標準方式產生一個有限或無限序列值,迭代器有next()對象;
3.多次返回可以被 next多次調用,最大特點是可以控制執行順序;

2.2 聲明方法

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 語句註明

2.3 執行

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)
}

2.4 yield和 return 的區別

相同點:
1.都能返回語句後面的那個表達式的值
2.都可以暫停函數執行
區別:
一個函數可以有多個 yield,但是只能有一個 return
yield 有位置記憶功能,return 沒有

2.5 throw

拋出錯誤,可以被try…catch…捕捉到

g.throw('這是拋出的一個錯誤');
// 這是拋出的一個錯誤

2.6 應用

// 要求:函數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);
}
}

2.7 PolyFill

原理圖

JS高級之手寫一個Promise,Generator,async和await【近1W字】

2.7.1 初級版

實現一個迭代器(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}

2.7.2 中級版

實現可迭代(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

2.7.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]]
}
}
}

3.async 和 await

3.1 async作用

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的‘*’

3.2 await作用

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對象,可以異步執行阻塞

3.3 async和await結合作用

1.主要是處理Promise的鏈式回調或函數的地獄回調
回到Generator中要求函數valOne,valTwo,valThree函數依次執行

function valOne(){}
function valTwo(){}
function valThree(){}
async ()=>{
await valOne()
await valTwo()
await valThree()
}

2.處理異常
try…catch…
或者await .catch()

3.4 和Generator的區別

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,用法有很多相似之處

3.5 執行器PolyFill

實現執行器兩種方式:
回調函數(Thunk 函數)
Promise 對象

3.5.1 初級版

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();
}

3.5.2 高級版

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); });
});
}

4.Promise,Generator,async和await對比

4.1 代碼

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…代碼更優雅

4.2 原理

async 和 await 是在 Generator 的基礎上封裝了自執行函數和一些特性;
具體對比見沒個 API 的 PolyFill

4.3 執行順序

來道頭條的面試

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!

相關文章

如何將相同屬性的數組對象合併並統計個數

高逼格面試:線程封閉,新名詞√

2020年的前端資源

再見2019展望未來|年度徵文