淺談JS異步編程: 回調地獄 --> Promise(重點說,附源碼) --> async+await

今天說一下JS的異步編程,jS異步共三個階段:最先的callback逐層嵌套(回調地獄),後來出了Promise以後風平浪靜了,沒過兩年又有人出來嘚瑟,封裝了一下Promise,變出來一個 async+await ,這個其實就是Promise的語法糖(Promise+generator = async+await ),下面分別說下:

一、原本想說回調地獄,後來發現我們仍是別浪費時間講這種被摒棄好久的東西,不如說下 發佈訂閱、觀察者模式(Promise用到了此模式)

  • 發佈訂閱:發佈者和訂閱者是沒有依賴關係的
  • 觀察者模式:觀察者和被觀察者有依賴關係的
  • 應用場景:雙向數據綁定 用的就是訂閱模式,可是如今用的更多的仍是觀察者模式實現

1.1 發佈訂閱模式,上代碼:javascript

let fs = require('fs');// 發佈 訂閱 發佈者和訂閱者是沒有依賴關係的let dep = {  arr:[],  on(callback){    this.arr.push(callback)  },  emit(){    this.arr.forEach(item => {      item();    });  }}let school = {}dep.on(function () {  if (Object.keys(school).length === 2) {    console.log(school);  }});fs.readFile('./age.txt', 'utf8', function (err, data) {  school['name'] = data;  dep.emit();});fs.readFile('./name.txt', 'utf8', function (err, data) {  school['age'] = data;  dep.emit();});複製代碼

1.2 發佈訂閱模式,上代碼:css

// 觀察者
class Observer{  constructor(){    this.arr = [];    this.val = 1; // 等待這個值更新時 觸發被觀察者的更新方法  }  updateVal(val){    this.val = val;    this.notify();  }  notify(){    this.arr.forEach(s => s.update(this.val));  }  save(s){    this.arr.push(s);  }}// 被觀察者,被觀察者有一個更新的方法class Subject{  constructor() {    this.small = '';  }  update(val){    this.small = val;    console.log(this);  }}let s = new Subject(); // 一個個小的被觀察者let s2 = new Subject(); // 一個個小的被觀察者let observe = new Observer();observe.save(s);observe.save(s2);observe.updateVal(100); // 當值發生變化後 會自動觸發複製代碼


二、Promise

構造函數Promise必須接受一個函數做爲參數,咱們稱該函數爲handlehandle又包含resolvereject兩個參數,它們是兩個函數。html

定義一個判斷一個變量是否爲函數的方法,後面會用到java

// 判斷變量否爲function
const isFunction = variable => typeof variable === 'function'
複製代碼複製代碼

首先,咱們定義一個名爲 MyPromiseClass,它接受一個函數 handle 做爲參數node

class MyPromise {
  constructor (handle) {
    if (!isFunction(handle)) {
      throw new Error('MyPromise must accept a function as a parameter')
    }
  }
}
複製代碼複製代碼

再往下看git

Promise 對象存在如下三種狀態:github

  • Pending(進行中)npm

  • Fulfilled(已成功)編程

  • Rejected(已失敗)redux

狀態只能由 Pending 變爲 Fulfilled 或由 Pending 變爲 Rejected ,且狀態改變以後不會在發生變化,會一直保持這個狀態。

Promise的值是指狀態改變時傳遞給回調函數的值

上文中handle函數包含 resolvereject 兩個參數,它們是兩個函數,能夠用於改變 Promise 的狀態和傳入 Promise 的值

new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('FULFILLED')
  }, 1000)
})
複製代碼複製代碼

這裏 resolve 傳入的 "FULFILLED" 就是 Promise 的值

resolvereject

  • resolve : 將Promise對象的狀態從 Pending(進行中) 變爲 Fulfilled(已成功)
  • reject : 將Promise對象的狀態從 Pending(進行中) 變爲 Rejected(已失敗)
  • resolvereject 均可以傳入任意類型的值做爲實參,表示 Promise 對象成功(Fulfilled)和失敗(Rejected)的值

瞭解了 Promise 的狀態和值,接下來,咱們爲 MyPromise 添加狀態屬性和值

首先定義三個常量,用於標記Promise對象的三種狀態

// 定義Promise的三種狀態常量
const PENDING = 'PENDING'
const FULFILLED = 'FULFILLED'
const REJECTED = 'REJECTED'
複製代碼複製代碼

再爲 MyPromise 添加狀態和值,並添加狀態改變的執行邏輯

class MyPromise {
  constructor (handle) {
    if (!isFunction(handle)) {
      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
  }
}
複製代碼複製代碼

這樣就實現了 Promise 狀態和值的改變。下面說一說 Promise 的核心: then 方法

Promise 對象的 then 方法接受兩個參數:

promise.then(onFulfilled, onRejected)
複製代碼複製代碼

參數可選

onFulfilledonRejected 都是可選參數。

  • 若是 onFulfilledonRejected 不是函數,其必須被忽略

onFulfilled 特性

    若是 onFulfilled 是函數:

  • promise 狀態變爲成功時必須被調用,其第一個參數爲 promise 成功狀態傳入的值( resolve 執行時傳入的值)
  • promise 狀態改變前其不可被調用
  • 其調用次數不可超過一次

onRejected 特性

    若是 onRejected 是函數:

  • promise 狀態變爲失敗時必須被調用,其第一個參數爲 promise 失敗狀態傳入的值( reject 執行時傳入的值)
  • promise 狀態改變前其不可被調用
  • 其調用次數不可超過一次

屢次調用

    then 方法能夠被同一個 promise 對象調用屢次

  • promise 成功狀態時,全部 onFulfilled 需按照其註冊順序依次回調
  • promise 失敗狀態時,全部 onRejected 需按照其註冊順序依次回調

返回

then 方法必須返回一個新的 promise 對象

promise2 = promise1.then(onFulfilled, onRejected);
複製代碼複製代碼

所以 promise 支持鏈式調用

promise1.then(onFulfilled1, onRejected1).then(onFulfilled2, onRejected2);
複製代碼複製代碼

這裏涉及到 Promise 的執行規則,包括「值的傳遞」和「錯誤捕獲」機制:

一、若是 onFulfilled 或者 onRejected 返回一個值 x ,則運行下面的 Promise 解決過程:[[Resolve]](promise2, x)

  • x 不爲 Promise ,則使 x 直接做爲新返回的 Promise 對象的值, 即新的onFulfilled 或者 onRejected 函數的參數.
  • xPromise ,這時後一個回調函數,就會等待該 Promise 對象(即 x )的狀態發生變化,纔會被調用,而且新的 Promise 狀態和 x 的狀態相同。

下面的例子用於幫助理解:

let promise1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve()
  }, 1000)
})
promise2 = promise1.then(res => {
  // 返回一個普通值
  return '這裏返回一個普通值'
})
promise2.then(res => {
  console.log(res) //1秒後打印出:這裏返回一個普通值
})
複製代碼複製代碼
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
})
複製代碼複製代碼

二、若是 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
})
複製代碼複製代碼

三、若是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)
})
複製代碼複製代碼

四、若是 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
})
複製代碼複製代碼

根據上面的規則,咱們來爲 完善 MyPromise

修改 constructor : 增長執行隊列

因爲 then 方法支持屢次調用,咱們能夠維護兩個數組,將每次 then 方法註冊時的回調函數添加到數組中,等待執行

constructor (handle) {
  if (!isFunction(handle)) {
    throw new Error('MyPromise must accept a function as a parameter')
  }
  // 添加狀態
  this._status = PENDING
  // 添加狀態
  this._value = undefined
  // 添加成功回調函數隊列
  this._fulfilledQueues = []
  // 添加失敗回調函數隊列
  this._rejectedQueues = []
  // 執行handle
  try {
    handle(this._resolve.bind(this), this._reject.bind(this)) 
  } catch (err) {
    this._reject(err)
  }
}
複製代碼複製代碼

添加then方法

首先,then 返回一個新的 Promise 對象,而且須要將回調函數加入到執行隊列中

// 添加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) => {
  })
}
複製代碼複製代碼

那返回的新的 Promise 對象何時改變狀態?改變爲哪一種狀態呢?

根據上文中 then 方法的規則,咱們知道返回的新的 Promise 對象的狀態依賴於當前 then 方法回調函數執行的狀況以及返回值,例如 then 的參數是否爲一個函數、回調函數執行是否出錯、返回值是否爲 Promise 對象。

咱們來進一步完善 then 方法:

// 添加then方法
then (onFulfilled, onRejected) {
  const { _value, _status } = this
  // 返回一個新的Promise對象
  return new MyPromise((onFulfilledNext, onRejectedNext) => {
    // 封裝一個成功時執行的函數
    let fulfilled = value => {
      try {
        if (!isFunction(onFulfilled)) {
          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 (!isFunction(onRejected)) {
          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
    }
  })
}
複製代碼複製代碼

這一部分可能不太好理解,讀者須要結合上文中 then 方法的規則來細細的分析。

接着修改 _resolve_reject :依次執行隊列中的函數

resolvereject 方法執行時,咱們依次提取成功或失敗任務隊列當中的函數開始執行,並清空隊列,從而實現 then 方法的屢次調用,實現的代碼以下:

// 添加resovle時執行的函數
_resolve (val) {
  if (this._status !== PENDING) return
  this._status = FULFILLED
  this._value = val
  // 依次執行成功隊列中的函數,並清空隊列
  const run = () => {
    let cb;
    while (cb = this._fulfilledQueues.shift()) {
      cb(val)
    }
  }
  // 爲了支持同步的Promise
  setTimeout(() => run(), 0)
}
// 添加reject時執行的函數
_reject (err) { 
  if (this._status !== PENDING) return
  this._status = REJECTED
  this._value = err
  // 依次執行失敗隊列中的函數,並清空隊列
  const run = () => {
    let cb;
    while (cb = this._rejectedQueues.shift()) {
      cb(err)
    }
  }
  // 爲了支持同步的Promise
  setTimeout(run, 0)
}
複製代碼複製代碼

這裏還有一種特殊的狀況,就是當 resolve 方法傳入的參數爲一個 Promise 對象時,則該 Promise 對象狀態決定當前 Promise 對象的狀態。

const p1 = new Promise(function (resolve, reject) {
  // ...
});

const p2 = new Promise(function (resolve, reject) {
  // ...
  resolve(p1);
})
複製代碼複製代碼

上面代碼中,p1p2 都是 Promise 的實例,可是 p2resolve方法將 p1 做爲參數,即一個異步操做的結果是返回另外一個異步操做。

注意,這時 p1 的狀態就會傳遞給 p2,也就是說,p1 的狀態決定了 p2 的狀態。若是 p1 的狀態是Pending,那麼 p2 的回調函數就會等待 p1 的狀態改變;若是 p1 的狀態已是 Fulfilled 或者 Rejected,那麼 p2 的回調函數將會馬上執行。

咱們來修改_resolve來支持這樣的特性

// 添加resovle時執行的函數
_resolve (val) {
  if (this._status !== PENDING) return
  this._status = FULFILLED
  this._value = val
  const callback = () => {
    // 依次執行隊列中的函數,並清空隊列
    const runFulfilled = (value) => {
      let cb;
      while (cb = this._fulfilledQueues.shift()) {
        cb(value)
      }
    }
    const runRejected = (value) => {
      let cb;
      while (cb = this._rejectedQueues.shift()) {
        cb(value)
      }
    }
    if (val instanceof MyPromise) {
      val.then(runFulfilled, runRejected)
    } else {
      runFulfilled(val)
    }
  }
  // 爲了支持同步的Promise
  setTimeout(callback, 0)
}
複製代碼複製代碼

這樣一個Promise就基本實現了,如今咱們來加一些其它的方法

catch 方法

至關於調用 then 方法, 但只傳入 Rejected 狀態的回調函數

catch (onRejected) {
  return this.then(undefined, onRejected)
}
複製代碼複製代碼

靜態 resolve 方法

// 添加靜態resolve方法
static resolve (value) {
  // 若是參數是MyPromise實例,直接返回這個實例
  if (value instanceof MyPromise) return value
  return new MyPromise(resolve => resolve(value))
}
複製代碼複製代碼

靜態 reject 方法

// 添加靜態reject方法
static reject (value) {
  return new MyPromise((resolve ,reject) => reject(value))
}
複製代碼複製代碼

靜態 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)
      })
    }
  })
}
複製代碼複製代碼

靜態 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)
      })
    }
  })
}
複製代碼複製代碼

finally 方法

finally 方法用於指定無論 Promise 對象最後狀態如何,都會執行的操做

finally (cb) {
  return this.then(
    value  => MyPromise.resolve(cb()).then(() => value),
    reason => MyPromise.resolve(cb()).then(() => { throw reason })
  );
};
複製代碼複製代碼

這樣一個完整的 Promsie 就實現了,你們對 Promise 的原理也有了解,可讓咱們在使用Promise的時候更加清晰明瞭。

完整代碼以下

// 判斷變量否爲function
const isFunction = variable => typeof variable === 'function'
// 定義Promise的三種狀態常量
const PENDING = 'PENDING'
const FULFILLED = 'FULFILLED'
const REJECTED = 'REJECTED'

class MyPromise {
  constructor (handle) {
    if (!isFunction(handle)) {
      throw new Error('MyPromise must accept a function as a parameter')
    }
    // 添加狀態
    this._status = PENDING
    // 添加狀態
    this._value = undefined
    // 添加成功回調函數隊列
    this._fulfilledQueues = []
    // 添加失敗回調函數隊列
    this._rejectedQueues = []
    // 執行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
    const callback = () => {
      // 依次執行隊列中的函數,並清空隊列
      const runFulfilled = (value) => {
        let cb;
        while (cb = this._fulfilledQueues.shift()) {
          cb(value)
        }
      }
      const runRejected = (value) => {
        let cb;
        while (cb = this._rejectedQueues.shift()) {
          cb(value)
        }
      }
      if (val instanceof MyPromise) {
        val.then(runFulfilled, runRejected)
      } else {
        runFulfilled(val)
      }
    }
    // 爲了支持同步的Promise
    setTimeout(callback, 0)
  }
  // 添加reject時執行的函數
  _reject (err) { 
    if (this._status !== PENDING) return
    this._status = REJECTED
    this._value = err
    // 依次執行失敗隊列中的函數,並清空隊列
    const run = () => {
      let cb;
      while (cb = this._rejectedQueues.shift()) {
        cb(err)
      }
    }
    // 爲了支持同步的Promise
    setTimeout(run, 0)
  }
  // 添加then方法
  then (onFulfilled, onRejected) {
    const { _value, _status } = this
    // 返回一個新的Promise對象
    return new MyPromise((onFulfilledNext, onRejectedNext) => {
      // 封裝一個成功時執行的函數
      let fulfilled = value => {
        try {
          if (!isFunction(onFulfilled)) {
            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 (!isFunction(onRejected)) {
            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
      }
    })
  }
  catch (onRejected) {
    return this.then(undefined, onRejected)
  }
  // 添加靜態resolve方法
  static resolve (value) {
    // 若是參數是MyPromise實例,直接返回這個實例
    if (value instanceof MyPromise) return value
    return new MyPromise(resolve => resolve(value))
  }
  // 添加靜態reject方法
  static reject (value) {
    return new MyPromise((resolve ,reject) => reject(value))
  }
  // 添加靜態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)
        })
      }
    })
  }
  // 添加靜態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)
        })
      }
    })
  }
  finally (cb) {
    return this.then(
      value  => MyPromise.resolve(cb()).then(() => value),
      reason => MyPromise.resolve(cb()).then(() => { throw reason })
    );
  }
}
複製代碼複製代碼


三、async+await

3.1 Generator

Generator是一個生成器,生成出一個迭代器,主要是用來控制異步流程的,目前在現有的庫中仍是比較少看到generator的,目前主要使用generator的是redux-saga這個庫,koa1.0也是用generator,可是如今都改成async/await。

generator生成器看起來很像普通函數,可是它是在函數名前加了一個 * 號

function* say(){  // 在函數名前加 *
    
}
複製代碼

生成器函數能夠暫停,而普通函數則是默認一路到底執行代碼,生成器函數在內部碰到yield就能夠實現暫停功能,使用next進行迭代

function* say(){
    yield 1
    yield 2
    yield 3
    return 'end'
}

// 1.執行這個生成器看起來跟執行普通函數差很少,
// 可是實際上,執行這個生成器,會返回一個迭代器
let it = say()  
                
// 2.此時it是一個迭代器 iterator,打印輸出是 {}
console.log(it)

let obj1 = it.next()
// 3. 使用next進行迭代,打印輸出 { value: 1, done: false }
// 能夠看出,咱們執行say生成器,用it來接收生成器返回的迭代器,
// 而經過迭代器it執行next(),會返回 { value: 1, done: false }
// 這裏的value表明的是上面yield後的值,依次返回,
// done爲false,意思是還沒結束,後面還有yield或者return,當走到return時
// done會變爲true,也就是完成了
console.log(obj1) 
複製代碼

咱們這樣完整看一下

function* say() {
  let a = yield 1 // 第一個it.next()時返回
  let b = yield 2 // 第二個it.next()時返回
  let c = yield 3 // 第三個it.next()時返回
  return 'end'    // 第四個it.next()時返回
}

let it = say()
let obj1 = it.next()
console.log(obj1) // { value: 1, done: false }
let obj2 = it.next()
console.log(obj2) // { value: 2, done: false }
let obj3 = it.next()
console.log(obj3) // { value: 3, done: false }
let obj4 = it.next()
console.log(obj4) // { value: 'end', done: true }
複製代碼

迭代器,要咱們自行一個一個去迭代,通常咱們會經過下面這樣進行迭代

function* say() {
  let a = yield 1 // 第一個it.next()時返回
  let b = yield 2 // 第二個it.next()時返回
  let c = yield 3 // 第三個it.next()時返回
  return 'end'    // 第四個it.next()時返回
}

let it = say()

function next(){
    let { value,done } = it.next()
    console.log(value) // 依次打印輸出 1 2 3 end
    if(!done) next() // 直到迭代完成
}
next()
複製代碼

經過上面的例子咱們大概明白generator大概是怎麼執行的了,
那麼下面咱們講講,怎麼往generator裏面放東西。

image
function* say() {
  let a = yield 'hello swr1'
  console.log(a)
  let b = yield 'hello swr2'
  console.log(b)
}

let it = say() // 返回迭代器

// 打印輸出 { value: 'hello swr1', done: false }
// 此時執行迭代器的第一個next,會把上圖紅色圈的區域執行,而且輸出'hello swr1'
// 此時須要注意的是let a = yield 'hello swr1',並不是是把yield 'hello swr1'
// 賦值給a,那麼a是何時被賦值呢?咱們接着看下面
console.log(it.next()) 

// 打印輸出 我是被傳進來的1
// { value: 'hello swr2', done: false }
// 此時咱們在next裏傳參,實際上就是當執行第二個next的時候,
// 會把上面藍色圈的區域執行,而這個next的參數,
// 會被賦值給a,而後執行console.log(a),而後把'hello swr2'輸出
console.log(it.next('我是被傳進來的1'))

// 打印輸出 我是被傳進來的2
// { value: undefined, done: true }
// 此時咱們第三次執行next,實際上就是當執行了第三個next的時候,
// 會把上面黃色圈的區域執行,而這個next的參數,
// 會被賦值給b,而後執行console.log(b),而後由於沒有顯式寫return xxx,
// 會被默認返回undefined
console.log(it.next('我是被傳進來的2'))
複製代碼

寫到這裏,我和你們同樣不解,這東西到底有什麼用,並且這樣一個一個next迭代,也很繁瑣,下面就來點實用的,generator能夠和promise配合使用。

3.2 generator和Promise、co配合使用

在講generator和Promise、co配合使用以前我會講一下promise化的函數怎麼寫,由於咱們平常開發當中,常常會使用到promise,用一次,就寫一大堆代碼也很差,那麼咱們會考慮到寫一個通用的函數,能夠把回調函數方式的函數,改成promise

// 假設咱們有3個文件,1.txt對應的內容爲文本'2.txt'
                   2.txt對應的內容爲文本'3.txt'
                   3.txt對應的內容爲文本'hello swr'

let fs = require('fs')

// promise化函數
function promisify(fn){
    return function(...args){
        return new Promise((resolve,reject)=>{
            fn(...args,(err,data)=>{ // node的api第一個參數爲err
                if(err) reject(err)
                resolve(data)
            })
        })
    }
}

// 把fs.readFile函數promise化
let read = promisify(fs.readFile)

read('1.txt','utf8').then((data)=>{
    console.log(data) // 打印輸出爲 '2.txt'
})
複製代碼

這樣咱們就完成了一個通用的promise化函數。

接下來咱們要去了解一下co庫

co庫地址:github.com/tj/co
$ npm install co

// 本處代碼promisify源用上面的函數promisify
// 本處代碼read源用上面的函數read
let co = require('co')
function* r(){
    let r1 = yield read('1.txt','utf8')
    let r2 = yield read(r1,'utf8')
    let r3 = yield read(r2,'utf8')
    return r3
}

// 此時咱們想取到r3,也就是3.txt裏的內容'hello swr'
// 方法一:
let it = r()
let { value,done } = it.next() // value爲一個promise對象
                               // 該對象會把resolve的值傳給下一個then
value.then((data)=>{ // data值爲'2.txt'
  let { value,done } = it.next(data)
  return value
}).then((data)=>{ // data值爲'3.txt'
  let { value,done } = it.next(data)
  return value
}).then((data)=>{ // data值爲'hello swr'
  console.log(data) // 打印輸出 'hello swr'
})
// 這樣的寫法,反而顯得很繁瑣複雜了,那麼咱們下面看下使用generator+co是怎麼使用的

// 方法二:
co(r()).then(data=>{
    console.log(data) // 打印輸出 'hello swr'
})
// 是否是發現generator+co很是高效?
// 代碼更像同步代碼了,那麼接下來咱們本身實現一個co~
複製代碼

co是如何實現呢?

function co(it){
    return new Promise((resolve,reject)=>{
        function next(data){
            let { value,done } = it.next(data)
            if(!done){
                value.then((data)=>{
                    next(data)
                },reject)
            }else{
                resolve(value)
            }
        }
        next()
    })
}
複製代碼

當有兩個gennerator函數時,而且其中一個嵌套另一個

function* a(){
    yield 1
}

function* b(){
    // 1. 當咱們想在generator b中嵌套generator a時,怎麼嵌套呢?
    // 2. yield *a(); ==> yield 1,實際上就是把yield 1 放在這個位置
    // 在生成器函數中使用生成器函數 須要使用 *
    yield *a()
    yield 2
}

let it = b()
console.log(it.next())複製代碼


3.3 出來吧! async await

// 假設咱們有3個文件,1.txt對應的內容爲文本'2.txt'
                   2.txt對應的內容爲文本'3.txt'
                   3.txt對應的內容爲文本'hello swr'
                   
async function r(){
    try{
        let r1 = await read('1.txt','utf8')
        let r2 = await read(r1,'utf8')
        let r3 = await read(r2,'utf8')
        return r3
    }catch(e){
        console.log(e)
    }
}

r().then((data)=>{
    console.log(data) // hello swr
})
複製代碼

有沒發現,async + await = generator + co?
async await解決了異步問題

  1. 可讓代碼像同步
  2. 可使用try catch
  3. 可使用promise
  4. 若是let r1 = await 後面等待的是promise,那麼會把promise的結果賦值給前面的r1,若是let r1 = await 後面等待的是普通值,那麼就會把這個普通值賦值給前面的r1
// 那麼我想把上面的3個請求改成併發的呢?
let arr = Promise.all([read('1.txt','utf8'),read('2.txt','utf8'),read('3.txt','utf8')])
複製代碼

那麼async await經過babel編譯後是怎樣的呢?

'use strict';

var r = function () {
    var _ref = _asyncToGenerator( // async await被編譯成generator /*#__PURE__*/regeneratorRuntime.mark(function _callee() {
        var r1, r2, r3;
        return regeneratorRuntime.wrap(function _callee$(_context) {
            while (1) {
                switch (_context.prev = _context.next) {
                    case 0:
                        _context.prev = 0;
                        _context.next = 3;
                        return read('100.txt', 'utf8');

                    case 3:
                        r1 = _context.sent;
                        _context.next = 6;
                        return read(r1, 'utf8');

                    case 6:
                        r2 = _context.sent;
                        _context.next = 9;
                        return read(r2, 'utf8');

                    case 9:
                        r3 = _context.sent;
                        return _context.abrupt('return', r3);

                    case 13:
                        _context.prev = 13;
                        _context.t0 = _context['catch'](0);

                        console.log(_context.t0);

                    case 16:
                    case 'end':
                        return _context.stop();
                }
            }
        }, _callee, this, [[0, 13]]);
    }));

    return function r() {
        return _ref.apply(this, arguments);
    };
}();

function _asyncToGenerator(fn) {
    return function () {
        var gen = fn.apply(this, arguments);
        return new Promise(function (resolve, reject) {
            function step(key, arg) { // 至關於咱們上門寫的next函數
                try {
                    var info = gen[key](arg);
                    var value = info.value;
                } catch (error) {
                    reject(error); return;
                } if (info.done) {
                    resolve(value);
                } else {
                    return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); });
                }
            } return step("next");
        });
    };
}
複製代碼

咱們從callback promise generator async + await基本瞭解了異步發展的進程了,
接下來咱們用一個例子來貫穿這幾個~
咱們有個需求,分別有3個球,一個球執行完動畫,纔會輪到下一個球執行動畫,以此類推

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style> .ball { position: absolute; width: 100px; height: 100px; background: red; border-radius: 50%; left: 0; } div:nth-child(1) { top: 0px } div:nth-child(2) { top: 120px } div:nth-child(3) { top: 240px } </style>
</head>

<body>
    <div>
        <div class="ball"></div>
        <div class="ball"></div>
        <div class="ball"></div>
    </div>
    <script> // 4.------------------// async await let balls = document.querySelectorAll('.ball'); function move(ele, distance) { return new Promise((resolve, reject) => { let movex = 0; let interval = setInterval(() => { movex++; ele.style.left = movex + 'px'; if (movex >= distance) { resolve(); clearInterval(interval); } }, 6) }) } async function m() { await move(balls[0], 500); await move(balls[1], 400); await move(balls[2], 300) } m().then(data=>{ alert('ok'); }); // 3.------------------// generator + co // let balls = document.querySelectorAll('.ball'); // function move(ele, distance) { // return new Promise((resolve, reject) => { // let movex = 0; // let interval = setInterval(() => { // movex++; // ele.style.left = movex + 'px'; // if (movex >= distance) { // resolve(); // clearInterval(interval); // } // }, 6) // }) // } // function* m() { // yield move(balls[0], 500); // yield move(balls[1], 400); // yield move(balls[2], 300) // } // function co(it) { // return new Promise((resolve, reject) => { // function next(data) { // let { value, done } = it.next(data); // if (!done) { // value.then(data => { // next(data); // }, reject); // }else{ // resolve(value); // } // } // next(); // }) // } // co(m()).then(data => { // alert('done') // }) // 2.------------------// promise // let balls = document.querySelectorAll('.ball'); // function move(ele, distance) { // return new Promise((resolve, reject) => { // let movex = 0; // let interval = setInterval(() => { // movex++; // ele.style.left = movex + 'px'; // if (movex >= distance) { // resolve(); // clearInterval(interval); // } // }, 6) // }) // } // move(balls[0],500).then(data=>{ // return move(balls[1],400) // }).then(data=>{ // return move(balls[2],300) // }).then(data=>{ // alert('ok'); // }) // 1.------------------// callback // let balls = document.querySelectorAll('.ball'); // function move(ele, distance, cb) { // let movex = 0; // let interval = setInterval(()=>{ // movex++; // ele.style.left = movex+'px'; // if(movex >= distance){ // cb(); // clearInterval(interval); // } // },6) // } // move(balls[0], 500, function () { // move(balls[1], 400, function () { // move(balls[2], 300, function () { // alert('成功') // }) // }) // }) </script>
</body>

</html>複製代碼

打完收功!歡迎批評,加關注,互相學習共同進步,我會堅持持續更新文章,加油吧!

相關文章
相關標籤/搜索