深入学习JavaScript系列(七)——Promise async/await generator

本篇属于本系列第七篇

 第一篇:#深入学习JavaScript系列(一)—— ES6中的JS执行上下文

第二篇:# 深入学习JavaScript系列(二)——作用域和作用域链

第三篇:# 深入学习JavaScript系列(三)——this

第四篇:# 深入学习JavaScript系列(四)——JS闭包

第五篇:# 深入学习JavaScript系列(五)——原型/原型链

第六篇: # 深入学习JavaScript系列(六)——对象/继承

第七篇:# 深入学习JavaScript系列(七)——Promise async/await generator

Promise属于js进阶的内容,我刚刚开始学习的时候 我是这样理解的: Promise是ES6中原生的一个方法,类似一个容器,代表着未来要发生的某件事情,属于异步操作的一种方法,这句话在我初学的时候听起来也是迷迷糊糊的 。

在阅读了很多篇文章后,发现promise的底层原理:callback回调函数+发布订阅模式

一 Promise

1.1 Promise概述

(1) 什么是promsie?

说的通俗易懂一点:从语法上讲:Promise是一个对象(类),从它可以获取异步操作的内容消息;从本意上讲,它是承诺,承诺它过一段时间会给你一种结果,Promise有三种状态:pending(等待),fulfiled(成功),rejected(失败);状态一旦改变,就不会再变,创造promise实例后,他会立即执行

如果还不明白,我把他打印出来:

深入学习JavaScript系列(七)——Promise  async/await   generator

从上图中可以看到 promise是一个对象,本身就有all reject,resolve等方法;还能看到promise的原型上有then,finally等方法。这几个方法也是我们后面需要研究和实现的点。

(2) promise怎么用?

来个简单的例子:

let p = new Promise(function(resolve, reject){
		//做一些异步操作
		setTimeout(function(){
			console.log('执行完成Promise');
			resolve('要返回的数据可以任何数据例如接口返回数据');
		}, 2000);
	});

promise接受一个函数参数,此函数中传入两个参数:resolve,reject,分别表示异步操作执行成功后的回调函数和异步操作执行失败后的回调函数。

但是我们日常使用不是这直接new 一个promise的,而是会在外面包裹一个函数,然后调用外面的函数,这样做的好处是我们return出去的依然是一个promise对象,还可以使用promise上的then,catch等方法。

function promiseFn(){
    var p = new Promise(function(resolve, reject){
        //做一些异步操作
        setTimeout(function(){
            console.log('执行完成');
            resolve('随便什么数据');
        }, 2000);
    });
    return p;            
}
promiseFn()

promiseFn().then(function(data){
    console.log(data);
    // 其他操作
    
});

(3) promise有什么特点?

1、promise的状态不受外界的影响,就像我开头说的是一个容器,除了异步操作的结果其他手段无法改变promise的状态。

2、状态一旦改变 就不会改变,任何时候都会得到这个结果,状态改变有两种: 从pending变为fulfilled和从pending变为rejected.

(4) promise解决了什么问题?

promise解决了两个主要的问题:

  • 解决回调地狱:代码难以维护,嵌套过多每一个函数的输出是第二个函数的输入这种现象
  • 实现多个并发请求,获取并发请求中的数据,(解决异步问题)

在传统的异步编程中,如果异步之间存在依赖关系,我们需要通过层层嵌套来实现这种依赖,如果嵌套层数过多,就会变成我们所说的回调地狱,promise可以把回调嵌套改成链式调用,

注: promise可以解决异步问题,但是不能说promise本身是异步的

通过上述几个问题,应该基本能了解promsie了,下面就来说promsie中几个重点

1.2 then

在上面的打印中我们可以知道 then是promsie原型链上的一个方法,
then其实就是解决promise中的回调问题的,promise通过then实现了链式调用,

then中有两个参数 :onFulfilled,onRejected 分别是成功的值,失败的原因。

  • 当状态state为fulfilled,则执行onFulfilled,传入this.value。当状态state为rejected,则执行onRejected,传入this.reason

  • onFulfilled,onRejected如果他们是函数,则必须分别在fulfilled,rejected后被调用,value或reason依次作为他们的第一个参数

then 的使用:

function promiseFn(){
    var p = new Promise(function(resolve, reject){
        //做一些异步操作
        setTimeout(function(){
            console.log('执行完成');
            resolve('随便什么数据');
        }, 2000);
    });
    return p;            
}
promiseFn()

promiseFn().then(function(data){
    console.log(data);
    // 其他操作
}).then(data=>{ 
console.log(data) //undefined 
return [1,2,3] 
}).then(data=>{ 
console.log(data) //[1,2,3]
})

那么then是怎么进行链式调用的呢?

  1. promise中使用.then()的方法来进行链式调用,通过.then()的回调拿到上一个.then()的返回值
  2. 链式调用要求.then()方法返回的必须是一个promise,这样才有.then()方法;
  3. 需要按照顺序执行,所以只有当前的promise状态变更后,才能执行下一个then方法

1.3 如何手写一个Promise

初步分析promise

首先我们来分析一个简单的promise例子

const p1 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('result')
    },
    1000);
}) 

p1.then(res => console.log(res), err => console.log(err))

实际上 promise的整个执行过程是:

  1. promise的构造方法接受一个executor(),在new Promise()时刻就立即执行了executor()回调,
  2. executor内部的异步任务被放入宏/微任务队列,等待执行。也就是上面代码中的定时器。
  3. 当then()被执行时,收集回调(成功或者失败) 同时放入成功或者失败队列。
  4. executor ()异步任务被执行,触发 resolve/reject 从成功/失败中取出回调依次执行

通过上面讲解执行过程可以看出 promise使用的是个观察者模式,也就是收集依赖 -触发通知-取出依赖执行的方式

总结:在promise中 执行顺序是then收集依赖 -异步触发resolve – resolve收集依赖

实现promise

在1.1中提到 promise本质上是一个类,所以使用class来声明promise

class MyPromise {
    // 构造方法接收一个回调
    constructor(executor) {
      this._resolveQueue = []    // then收集的执行成功的回调队列
      this._rejectQueue = []     // then收集的执行失败的回调队列
  
      // 由于resolve/reject是在executor内部被调用,
      // 因此需要使用箭头函数固定this指向, 否则找不到this._resolveQueue
      let _resolve = (val) => {
        // 从成功队列里取出回调依次执行
        while(this._resolveQueue.length) {
          const callback = this._resolveQueue.shift()
          callback(val)
        }
      }
      // 实现同resolve
      let _reject = (val) => {
        while(this._rejectQueue.length) {
          const callback = this._rejectQueue.shift()
          callback(val)
        }
      }
      // new Promise()时立即执行executor,并传入resolve和reject
      executor(_resolve, _reject)
    }
  
    // then方法,接收一个成功的回调和一个失败的回调,并push进对应队列
    then(resolveFn, rejectFn) {
      this._resolveQueue.push(resolveFn)
      this._rejectQueue.push(rejectFn)
    }
  }

在上面的代码中 我们通过构造方法 创建了两个队列:resolveQueue rejectQueue 用来收集执行成功/失败后的回调队列, 然后依次判断并取出作为resolve和reject的回调 最后在调用then方法的时候返回即可,着就是promise的基础功能 实现了resolve 和rejcet

实现promiseA+

当然 我们还要继续往下写:在Es6中 Promise需要遵循 PromiseA+规范,要求对promise的状态进行控制 。

PromiseA+规范很多,核心规则有两个:

  1. Promise的状态有且只有三种: Pending(等待)。Fulfilled(已完成)。 Rejected(拒绝)。
  2. then有两个参数可选,分别对应状态改变时触发的回调,then方法返回一个promise 同时then方法可以被同一个promise调用很多次

加上promiseA+规范后,我们的代码是这样的:

//Promise/A+规范的三种状态
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

class MyPromise {
  // 构造方法接收一个回调
  constructor(executor) {
    this._status = PENDING     // Promise状态 初始的时候
    this._resolveQueue = []    // 成功队列, resolve时触发
    this._rejectQueue = []     // 失败队列, reject时触发

    // 由于resolve/reject是在executor内部被调用, 因此需要使用箭头函数固定this指向, 否则找不到this._resolveQueue
    let _resolve = (val) => {
      if(this._status !== PENDING) return   // 对应规范中的"状态只能由pending到fulfilled或rejected"
      this._status = FULFILLED              // 变更状态

      // 这里之所以使用一个队列来储存回调,是为了实现规范要求的 "then 方法可以被同一个 promise 调用多次"
      // 如果使用一个变量而非队列来储存回调,那么即使多次p1.then()也只会执行一次回调
      while(this._resolveQueue.length) {    
        const callback = this._resolveQueue.shift()
        callback(val)
      }
    }
    // 实现同resolve
    let _reject = (val) => {
      if(this._status !== PENDING) return   // 对应规范中的"状态只能由pending到fulfilled或rejected"
      this._status = REJECTED               // 变更状态
      while(this._rejectQueue.length) {
        const callback = this._rejectQueue.shift()
        callback(val)
      }
    }
    // new Promise()时立即执行executor,并传入resolve和reject
    executor(_resolve, _reject)
  }

  // then方法,接收一个成功的回调和一个失败的回调
  then(resolveFn, rejectFn) {
    this._resolveQueue.push(resolveFn)
    this._rejectQueue.push(rejectFn)
  }
}

解释:

  1. 先初始化的时候增加三种状态
  2. 在进入constructor构造函数时把状态设置为PENDING
  3. 在执行_resolve 和_reject时需要判断当前状态是否为PENDING,如果不是直接return出去
  4. 在进入_resolve 和_reject流程后把状态对应改为FULFILLED 和REJECTED。

实现then方法

接下来到了 链式调用;上面我们提到 promsie采用了链式调用的方法来改变回调地狱
前面分析过链式调用的步骤:

  1. promise中使用.then()的方法来进行链式调用,通过.then()的回调拿到上一个.then()的返回值
  2. 链式调用要求.then()方法返回的必须是一个promise,这样才有.then()方法;
  3. 需要按照顺序执行,所以只有当前的promise状态变更后,才能执行下一个then方法

接下来就按照这个步骤实现then

注意: 在使用then方法的时候,我们需要判断 传入的参数是否是function;同时要判断promise的三种状态对应不同的写法

// then方法,接收一个成功的回调和一个失败的回调
  then(resolveFn, rejectFn) {
    // 根据规范,如果then的参数不是function,则我们需要忽略它, 让链式调用继续往下执行
    typeof resolveFn !== 'function' ? resolveFn = value => value : null
    typeof rejectFn !== 'function' ? rejectFn = reason => {
      throw new Error(reason instanceof Error? reason.message:reason);
    } : null
  
    // return一个新的promise
    return new MyPromise((resolve, reject) => {
      // 把resolveFn重新包装一下,再push进resolve执行队列,这是为了能够获取回调的返回值进行分类讨论
      const fulfilledFn = value => {
        try {
          // 执行第一个(当前的)Promise的成功回调,并获取返回值
          let x = resolveFn(value)
          // 分类讨论返回值,如果是Promise,那么等待Promise状态变更,否则直接resolve
          x instanceof MyPromise ? x.then(resolve, reject) : resolve(x)
        } catch (error) {
          reject(error)
        }
      }
  
      // reject同理
      const rejectedFn  = error => {
        try {
          let x = rejectFn(error)
          x instanceof MyPromise ? x.then(resolve, reject) : resolve(x)
        } catch (error) {
          reject(error)
        }
      }
  
      switch (this._status) {
        // 当状态为pending时,把then回调push进resolve/reject执行队列,等待执行
        case PENDING:
          this._resolveQueue.push(fulfilledFn)
          this._rejectQueue.push(rejectedFn)
          break;
        // 当状态已经变为resolve/reject时,直接执行then回调
        case FULFILLED:
          fulfilledFn(this._value)    // this._value是上一个then回调return的值(见完整版代码)
          break;
        case REJECTED:
          rejectedFn(this._value)
          break;
      }
    })
  }

promise+then实现

//Promise/A+规定的三种状态
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

class MyPromise {
  // 构造方法接收一个回调
  constructor(executor) {
    this._status = PENDING     // Promise状态
    this._value = undefined    // 储存then回调return的值
    this._resolveQueue = []    // 成功队列, resolve时触发
    this._rejectQueue = []     // 失败队列, reject时触发

    // 由于resolve/reject是在executor内部被调用, 因此需要使用箭头函数固定this指向, 否则找不到this._resolveQueue
    let _resolve = (val) => {
      //把resolve执行回调的操作封装成一个函数,放进setTimeout里,以兼容executor是同步代码的情况
      const run = () => {
        if(this._status !== PENDING) return   // 对应规范中的"状态只能由pending到fulfilled或rejected"
        this._status = FULFILLED              // 变更状态
        this._value = val                     // 储存当前value

        // 这里之所以使用一个队列来储存回调,是为了实现规范要求的 "then 方法可以被同一个 promise 调用多次"
        // 如果使用一个变量而非队列来储存回调,那么即使多次p1.then()也只会执行一次回调
        while(this._resolveQueue.length) {    
          const callback = this._resolveQueue.shift()
          callback(val)
        }
      }
      setTimeout(run)
    }
    // 实现同resolve
    let _reject = (val) => {
      const run = () => {
        if(this._status !== PENDING) return   // 对应规范中的"状态只能由pending到fulfilled或rejected"
        this._status = REJECTED               // 变更状态
        this._value = val                     // 储存当前value
        while(this._rejectQueue.length) {
          const callback = this._rejectQueue.shift()
          callback(val)
        }
      }
      // 这里的定时器是为了 保证promise的执行顺序 下面的也是
      setTimeout(run)
    }
    // new Promise()时立即执行executor,并传入resolve和reject
    executor(_resolve, _reject)
  }

  // then方法,接收一个成功的回调和一个失败的回调
  then(resolveFn, rejectFn) {
    // 根据规范,如果then的参数不是function,则我们需要忽略它, 让链式调用继续往下执行
    typeof resolveFn !== 'function' ? resolveFn = value => value : null
    typeof rejectFn !== 'function' ? rejectFn = reason => {
      throw new Error(reason instanceof Error? reason.message:reason);
    } : null
  
    // return一个新的promise
    return new MyPromise((resolve, reject) => {
      // 把resolveFn重新包装一下,再push进resolve执行队列,这是为了能够获取回调的返回值进行分类讨论
      const fulfilledFn = value => {
        try {
          // 执行第一个(当前的)Promise的成功回调,并获取返回值
          let x = resolveFn(value)
          // 分类讨论返回值,如果是Promise,那么等待Promise状态变更,否则直接resolve
          x instanceof MyPromise ? x.then(resolve, reject) : resolve(x)
        } catch (error) {
          reject(error)
        }
      }
  
      // reject同理
      const rejectedFn  = error => {
        try {
          let x = rejectFn(error)
          x instanceof MyPromise ? x.then(resolve, reject) : resolve(x)
        } catch (error) {
          reject(error)
        }
      }
  
      switch (this._status) {
        // 当状态为pending时,把then回调push进resolve/reject执行队列,等待执行
        case PENDING:
          this._resolveQueue.push(fulfilledFn)
          this._rejectQueue.push(rejectedFn)
          break;
        // 当状态已经变为resolve/reject时,直接执行then回调
        case FULFILLED:
          fulfilledFn(this._value)    // this._value是上一个then回调return的值(见完整版代码)
          break;
        case REJECTED:
          rejectedFn(this._value)
          break;
      }
    })
  }
}

注:最后的实现和上面两个相比加了定时器的处理,注意仔细查看,是为了处理promise的返回顺序。

promise的其他方法实现

promise.prototype.catch()

catch()方法返回一个promise 并且处理拒绝的情况

//catch方法其实就是执行一下then的第二个回调
catch(rejectFn) {
  return this.then(undefined, rejectFn)
}

promise.prototype.finally()

finally()方法返回一个promise 在promise结束后 ,无论结果是fulfilled或者 rejected 都会执行指定的回调函数, 在finally之后 花可以继续使用then,

//finally方法
finally(callback) {
    return this.then(
      value => MyPromise.resolve(callback()).then(() => value),  
      // MyPromise.resolve执行回调,并在then中return结果传递给后面的Promise
      reason => MyPromise.resolve(callback()).then(() => { throw reason }) 
      // reject同理
    )
  }
  

promise.resolve()
promise.resolve(value)方法返回一个已给定值解析后的promise对象 如果该值为promise 返回这个promise;如果这个值为then方法, 返回的promise会跟随这个方法的对象,采用它的最终状态,否则返回的promise将以此值完成。

//静态的resolve方法
static resolve(value) {
  if(value instanceof MyPromise) return value // 根据规范, 如果参数是Promise实例, 直接return这个实例
  return new MyPromise(resolve => resolve(value))
}

Promise.reject()

Promise.reject()方法返回一个带有拒绝原因的Promise对象。

//静态的reject方法
static reject(reason) {
  return new MyPromise((resolve, reject) => reject(reason))
}
复制代码

Promise.all()

Promise.all(iterable)方法返回一个 Promise 实例,此实例在 iterable 参数内所有的 promise 都“完成(resolved)”或参数中不包含 promise 时回调完成(resolve);如果参数中 promise 有一个失败(rejected),此实例回调失败(reject),失败原因的是第一个失败 promise 的结果。

//静态的all方法
static all(promiseArr) {
  let index = 0
  let result = []
  return new MyPromise((resolve, reject) => {
    promiseArr.forEach((p, i) => {
      //Promise.resolve(p)用于处理传入值不为Promise的情况
      MyPromise.resolve(p).then(
        val => {
          index++
          result[i] = val
          //所有then执行后, resolve结果
          if(index === promiseArr.length) {
            resolve(result)
          }
        },
        err => {
          //有一个Promise被reject时,MyPromise的状态变为reject
          reject(err)
        }
      )
    })
  })
}
复制代码

Promise.race()

Promise.race(iterable)方法返回一个 promise,一旦迭代器中的某个promise解决或拒绝,返回的 promise就会解决或拒绝。

static race(promiseArr) {
  return new MyPromise((resolve, reject) => {
    //同时执行Promise,如果有一个Promise的状态发生改变,就变更新MyPromise的状态
    for (let p of promiseArr) {
      MyPromise.resolve(p).then(  //Promise.resolve(p)用于处理传入值不为Promise的情况
        value => {
          resolve(value)        //注意这个resolve是上边new MyPromise的
        },
        err => {
          reject(err)
        }
      )
    }
  })
}

完整promise代码

//Promise/A+规定的三种状态
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class MyPromise {
// 构造方法接收一个回调
constructor(executor) {
this._status = PENDING     // Promise状态
this._value = undefined    // 储存then回调return的值
this._resolveQueue = []    // 成功队列, resolve时触发
this._rejectQueue = []     // 失败队列, reject时触发
// 由于resolve/reject是在executor内部被调用, 因此需要使用箭头函数固定this指向, 否则找不到this._resolveQueue
let _resolve = (val) => {
//把resolve执行回调的操作封装成一个函数,放进setTimeout里,以兼容executor是同步代码的情况
const run = () => {
if(this._status !== PENDING) return   // 对应规范中的"状态只能由pending到fulfilled或rejected"
this._status = FULFILLED              // 变更状态
this._value = val                     // 储存当前value
// 这里之所以使用一个队列来储存回调,是为了实现规范要求的 "then 方法可以被同一个 promise 调用多次"
// 如果使用一个变量而非队列来储存回调,那么即使多次p1.then()也只会执行一次回调
while(this._resolveQueue.length) {    
const callback = this._resolveQueue.shift()
callback(val)
}
}
setTimeout(run)
}
// 实现同resolve
let _reject = (val) => {
const run = () => {
if(this._status !== PENDING) return   // 对应规范中的"状态只能由pending到fulfilled或rejected"
this._status = REJECTED               // 变更状态
this._value = val                     // 储存当前value
while(this._rejectQueue.length) {
const callback = this._rejectQueue.shift()
callback(val)
}
}
setTimeout(run)
}
// new Promise()时立即执行executor,并传入resolve和reject
executor(_resolve, _reject)
}
// then方法,接收一个成功的回调和一个失败的回调
then(resolveFn, rejectFn) {
// 根据规范,如果then的参数不是function,则我们需要忽略它, 让链式调用继续往下执行
typeof resolveFn !== 'function' ? resolveFn = value => value : null
typeof rejectFn !== 'function' ? rejectFn = reason => {
throw new Error(reason instanceof Error? reason.message:reason);
} : null
// return一个新的promise
return new MyPromise((resolve, reject) => {
// 把resolveFn重新包装一下,再push进resolve执行队列,这是为了能够获取回调的返回值进行分类讨论
const fulfilledFn = value => {
try {
// 执行第一个(当前的)Promise的成功回调,并获取返回值
let x = resolveFn(value)
// 分类讨论返回值,如果是Promise,那么等待Promise状态变更,否则直接resolve
x instanceof MyPromise ? x.then(resolve, reject) : resolve(x)
} catch (error) {
reject(error)
}
}
// reject同理
const rejectedFn  = error => {
try {
let x = rejectFn(error)
x instanceof MyPromise ? x.then(resolve, reject) : resolve(x)
} catch (error) {
reject(error)
}
}
switch (this._status) {
// 当状态为pending时,把then回调push进resolve/reject执行队列,等待执行
case PENDING:
this._resolveQueue.push(fulfilledFn)
this._rejectQueue.push(rejectedFn)
break;
// 当状态已经变为resolve/reject时,直接执行then回调
case FULFILLED:
fulfilledFn(this._value)    // this._value是上一个then回调return的值(见完整版代码)
break;
case REJECTED:
rejectedFn(this._value)
break;
}
})
}
//catch方法其实就是执行一下then的第二个回调
catch(rejectFn) {
return this.then(undefined, rejectFn)
}
//finally方法
finally(callback) {
return this.then(
value => MyPromise.resolve(callback()).then(() => value),             //执行回调,并returnvalue传递给后面的then
reason => MyPromise.resolve(callback()).then(() => { throw reason })  //reject同理
)
}
//静态的resolve方法
static resolve(value) {
if(value instanceof MyPromise) return value //根据规范, 如果参数是Promise实例, 直接return这个实例
return new MyPromise(resolve => resolve(value))
}
//静态的reject方法
static reject(reason) {
return new MyPromise((resolve, reject) => reject(reason))
}
//静态的all方法
static all(promiseArr) {
let index = 0
let result = []
return new MyPromise((resolve, reject) => {
promiseArr.forEach((p, i) => {
//Promise.resolve(p)用于处理传入值不为Promise的情况
MyPromise.resolve(p).then(
val => {
index++
result[i] = val
if(index === promiseArr.length) {
resolve(result)
}
},
err => {
reject(err)
}
)
})
})
}
//静态的race方法
static race(promiseArr) {
return new MyPromise((resolve, reject) => {
//同时执行Promise,如果有一个Promise的状态发生改变,就变更新MyPromise的状态
for (let p of promiseArr) {
MyPromise.resolve(p).then(  //Promise.resolve(p)用于处理传入值不为Promise的情况
value => {
resolve(value)        //注意这个resolve是上边new MyPromise的
},
err => {
reject(err)
}
)
}
})
}
}

二 Generator

ES6 新引入Generator函数,也可以说是一种数据类型;最大的特点就是暂停执行。

关键词是function后面有一个*

Generato原理

1、 Generator可以通过yield关键字,把函数的执行流挂起,挂起的函数不会马上就执行,而是返回一个指向内部状态的指针对象;

2、 这个对象是一个遍历器(iterator)对象,iterator对象上有一个next()方法。

3、通过调用next()方法切换到下一个状态,移动内部指针,使得指针指向下一个状态。

4、next()同时会返回一个对象,表示当前阶段的信息- 其中 value 属性是 yield 语句后面表达式的值,表示当前阶段的值;done 属性表示 Generator 函数是否执行完毕,即是否还有下一个阶段

5、 返回遍历器对象有个 throw 方法可以抛出错误,抛出的错误可以被函数体内的 try/catch 代码块捕获

:yield 只能在 Generator 中使用,在其他地方使用会报错

function* myGenerator() {
yield '1'
yield '2'
return '3'
}
const gen = myGenerator();  // 获取迭代器
gen.next()  //{value: "1", done: false}
gen.next()  //{value: "2", done: false}
gen.next()  //{value: "3", done: true}

也可以通过给next()传参, 让yield具有返回值

function* myGenerator() {
console.log(yield '1')  //test1
console.log(yield '2')  //test2
console.log(yield '3')  //test3
}
// 获取迭代器
const gen = myGenerator();
gen.next()
gen.next('test1')
gen.next('test2')
gen.next('test3')

那么具体Generator是怎么执行的呢?
这里参考:链接
中方法,转化为es5 方法后代码如下:

"use strict";
var _marked =
/*#__PURE__*/
// mark()是内置的一个方法 为生成器绑定了一系列的原型
regeneratorRuntime.mark(foo);
function foo() {
// wrap() return一个方法
return regeneratorRuntime.wrap(function foo$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
_context.next = 2;
return 'result1';
case 2:
_context.next = 4;
return 'result2';
case 4:
_context.next = 6;
return 'result3';
case 6:
case "end":
return _context.stop();
}
}
}, _marked);
}
var gen = foo();
console.log(gen.next().value);
console.log(gen.next().value);
console.log(gen.next().value);

分析一下上面的代码会发现,Generator的实现核心在于用一个context对象储存上下文,

每一次yield的时候;函数并没有被真正挂起,在执行过程中都传入一遍生成器函数,然后用context存储上下文,每次执行生成器函数的时候,都可以从上一个执行结果开始执行,看上去就像函数被挂起一样。

调用流程如下:

  • 我们定义的function* 生成器函数被转化为以上代码

  • 转化后的代码分为三大块:

    • gen$(_context)由yield分割生成器函数代码而来
    • context对象用于储存函数执行上下文
    • invoke()方法定义next(),用于执行gen$(_context)来跳到下一步
  • 当我们调用g.next(),就相当于调用invoke()方法,执行gen$(_context),进入switch语句,switch根据context的标识,执行对应的case块,return对应结果

  • 当生成器函数运行到末尾(没有下一个yield或已经return),switch匹配不到对应代码块,就会return空值,这时g.next()返回{value: undefined, done: true}

二 async/await

async/await实现

首先 async/await实际上是对Generator的封装,是一个语法糖 我们对Generator不熟悉的原因是出现不久后就被更方便的async/await所取代了

我们在Generator章节分析到,Generator中yiled的用法和async/await的用法有很多相同的地方,都提供了暂停执行的功能,但是具体的不同有三点:

  1. async/await自带执行器 不需要手动next()就可以执行下一步
  2. async函数返回的值直接是一个promise对象,Genreator返回的是生成器对象
  3. await能够返回Promise的resolve/reject的值

所以我们在实现async/await时 也是在Generator上增加上面三点功能

自动执行

在Generator中 手动执行如下代码:

function* myGenerator() {
yield Promise.resolve(1);
yield Promise.resolve(2);
yield Promise.resolve(3);
}
// 手动执行迭代器
const gen = myGenerator()
gen.next().value.then(val => {
console.log(val)
gen.next().value.then(val => {
console.log(val)
gen.next().value.then(val => {
console.log(val)
})
})
})
//输出1 2 3

这里面发现,要实现手动执行就得使用尾调用的方式嵌套调用then方法;所以我们要实现生成器能自动往下执行,并且yield能放回resolve的值,

注意:async/await是关键词 不能重写,下面使用函数来模拟

function run(gen) {
var g = gen()                    
//由于每次gen()获取到的都是最新的迭代器,因此获取迭代器操作要放在_next()之前,否则会进入死循环
function _next(val) {            
//封装一个方法, 递归执行g.next()
var res = g.next(val)           
//获取迭代器对象,并返回resolve的值
if(res.done) return res.value   
//递归终止条件
res.value.then(val => {        
//Promise的then方法是实现自动迭代的前提
_next(val)                   
//等待Promise完成就自动执行下一个next,并传入resolve的值
})
}
_next() 
//第一次执行
}

在上面的代码中 我们通过判断条件 来实现循环调用,把下一步的操作封装成——next(),每次Promise.then()的时候都执行——next(),实现循环调用的效果, 这样可以简化上一个代码块如下:

function* myGenerator() {
console.log(yield Promise.resolve(1))   //1
console.log(yield Promise.resolve(2))   //2
console.log(yield Promise.resolve(3))   //3
}
run(myGenerator)

返回Promise和异常处理
这两个也是上面提到的特点;

返回promise:这里我们可以把上述代码中的(gen().next.value)都用Promise.resolve()转化一遍

异常处理:如果promise执行失败,导致后续的执行中断,采用Generator.prototype.throw()来抛出错误,这样能被外层的try-catch捕获到

所以这里我们改造一下上面一段代码:

function run(gen) {
//把返回值包装成promise
return new Promise((resolve, reject) => {
var g = gen()
function _next(val) {
//错误处理
try {
var res = g.next(val)
} catch (err) {
return reject(err);
}
if (res.done) {
return resolve(res.value);
}
//res.value包装为promise,以兼容yield后面跟基本类型的情况
Promise.resolve(res.value).then(
val => {
_next(val);
},
err => {
//抛出错误
g.throw(err)
});
}
_next();
});
}

测试:

function* myGenerator() {
try {
console.log(yield Promise.resolve(1)) 
console.log(yield 2)   //2
console.log(yield Promise.reject('error'))
} catch (error) {
console.log(error)
}
}
const result = run(myGenerator)     //result是一个Promise
//输出 1 2 error

写到最后发现,主要去讲他们怎么实现,都8000多字,尴尬,很多概念还没讲。深入学习之后发现每一个点都能写很多,所以只是写一些主要知识点,在实现后也能看出他们的原理,以及概念在运行过程中是怎么用的。文章是在学习过程中写的,有很多概念可能有错,如果有大佬能指出不胜感激!!

参考链接:

# 9k字 | Promise/async/Generator实现原理解析

# BAT前端经典面试问题:史上最最最详细的手写Promise教程

原文链接:https://juejin.cn/post/7215528103468138556 作者:十九万里

(0)
上一篇 2023年3月29日 上午10:21
下一篇 2023年3月29日 上午10:32

相关推荐

发表评论

登录后才能评论