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

前言

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

源碼

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

若是以爲看文章太囉嗦,能夠直接 git clone ,直接看代碼git

1.Promise

1.1 做用

Promise 你們應該都用過,ajax 庫就是利用 Promise封裝的;
做用主要是解決地獄回調問題.github

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 就會執行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.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可選的函數promise

2.不是函數必須被忽略bash

3.onFullfilled: A.當 promise 狀態變爲成功時必須被調用,其第一個參數爲 promise 成功狀態傳入的值( resolve 執行時傳入的值; B.在 promise 狀態改變前其不可被調用
C.其調用次數不可超過一次koa

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 被執行完成,因此donetrue
複製代碼

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

原理圖

Generator 原理.png

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!

相關文章
相關標籤/搜索