浅谈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>复制代码

打完收功!欢迎批评,加关注,互相学习共同进步,我会坚持持续更新文章,加油吧!

相关文章
相关标签/搜索