深入学习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实例后,他会立即执行

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

image.png

从上图中可以看到 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教程

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/6349.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

ChatGPT探索系列之二:学习GPT模型系列的发展历程和原理

文章目录前言一、GPT的起源GPT系列二、GPT的原理1. GPT原理:自注意2. GPT原理:位置编码3. GPT原理:Masked Language Modeling4. GPT原理:预训练5. GPT原理:微调6. GPT原理:多任务学习三、GPT模型的风险与挑…

二叉搜索树BST的学习

文章目录二叉搜索树BST什么是BST?用BST做什么?一、BST的特性BST的特性是什么?1.[230. 二叉搜索树中第K小的元素](https://leetcode.cn/problems/kth-smallest-element-in-a-bst/)2.[538. 把二叉搜索树转换为累加树](https://leetcode.cn/prob…

Git Commit Message 应该怎么写?

原文链接: Git Commit Message 应该怎么写? 最近被同事吐槽了,说我代码提交说明写的太差。其实都不用他吐槽,我自己心里也非常清楚。毕竟很多时候犯懒,都是直接一个 -m "fix" 就提交上去了。 这样做是非常…

C语言变量

C 变量 变量其实只不过是程序可操作的存储区的名称。C 中每个变量都有特定的类型,类型决定了变量存储的大小和布局,该范围内的值都可以存储在内存中,运算符可应用于变量上。 变量的名称可以由字母、数字和下划线字符组成。它必须以字母或下…

机器学习-卷积神经网络CNN中的单通道和多通道图片差异

背景 最近在使用CNN的场景中,既有单通道的图片输入需求,也有多通道的图片输入需求,因此又整理回顾了一下单通道或者多通道卷积的差别,这里记录一下探索过程。 结论 直接给出结论,单通道图片和多通道图片在经历了第一…

【hello C语言】结构体(下)

目录 1.结构体的声明 1.1 结构的声明 1.2 特殊声明:匿名结构体 1.3 结构的自引用 2. 结构体的定义和初始化 3. 结构体的内存对齐 3.1 内存对齐规则 3.2 内存对齐存在的原因 3.3 修改默认对其数 4. 结构体传参 C语言🛴 1.结构体的声明 结构体便是描述复杂…

一种适合容器化部署的雪花算法ID生成器

雪花算法简介 SnowFlake 中文意思为雪花,故称为雪花算法。最早是 Twitter 公司在其内部用于分布式环境下生成唯一 ID。 雪花算法有以下几个优点: 高并发分布式环境下生成不重复 id,每秒可生成百万个不重复 id。基于时间戳,以及同…

零编程经验,通过 GPT-4 十分钟开发了一个浏览器插件,并成功运行,实现了需求目标!

大佬蓝鸟ID: sundyme零编程经验,通过 GPT-4 十分钟开发了一个浏览器插件,并成功运行,实现了需求目标!太不可思意了,真正体会到了自然语言编程的魅力! 下一步是利用Pinterest 的 API 接口实现自动发图&#…

No.026<软考>《(高项)备考大全》【第10章】项目沟通和干系人管理(第2部分-干系人管理)

1 干系人管理部分相关 1.1 干系人ITO 1.2 干系人管理 过程过程的定义过程的作用识别干系人识别能影响项目决策、活动或结果的个人、群体或组织,以及被项目决策、活动或者结果影响的个人、群体或者组织,并分析和记录他们的相关信息的过程帮助项目经理建…

此战成硕,我成功上岸西南交通大学了~~~

友友们,好久不见,很长时间没有更一个正式点的文章了! 是因为我在去年年底忙着准备初试,今年年初在准备复试,直到3月底拟录取后,终于可以写下这篇上岸贴,和大家分享一下考研至上岸的一个过程 文章…

游戏算法-游戏AI行为树,python实现

参考文章:Behavior trees for AI: How they work (gamedeveloper.com) 本文主要参考上述weizProject Zomboid 的开发者 Chris Simpson文章的概念,用伪代码实现代码例子 AI概述 游戏AI是对游戏内所有非玩家控制角色的行为进行研究和设计,使得游…

电子拣货标签9代系统简介

CK_Label_v9一、产品参数 产品型号 CK_Label_v9 LED 3(红&黄&绿)独立可控 供电方式 DC 24V 0.2A 通信方式 无线通信 蜂鸣器 支持 尺寸 D60 H307mm 二、革新点 配合标签拣货使用三个灯(红黄绿)都可以被独立控…

基于MATALB编程的BP神经网络手臂血管分类识别,基于BP神经网络的图像分类

目标 背影 BP神经网络的原理 BP神经网络的定义 BP神经网络的基本结构 BP神经网络的神经元 BP神经网络的激活函数, BP神经网络的传递函数 数据 神经网络参数 基于BP神经网络手臂血管识别的MATLAB代码 效果图 结果分析 展望 背影 随着人工智能的发展,智…

贪心算法-删数问题C++

目录 一、题目 二&#xff1a;思路 代码 运行结果 一、题目 有一个长度为n&#xff08;n < 240&#xff09;的正整数&#xff0c;从中取出k&#xff08;k < n&#xff09;个数&#xff0c;使剩余的数保持原来的次序不变&#xff0c;求这个正整数经过删数之后最小是多…

用Python绘制六种可视化图表,简直太好用了

前言 嗨喽~大家好呀&#xff0c;这里是魔王呐 ❤ ~! python资料、源码、教程: 点击此处跳转文末名片获取 可视化图表&#xff0c;有相当多种&#xff0c;但常见的也就下面几种&#xff0c;其他比较复杂一点&#xff0c;大都也是基于如下几种进行组合&#xff0c;变换出来的。 …

记录--CSS 如何实现羽化效果?

这里给大家分享我在网上总结出来的一些知识&#xff0c;希望对大家有所帮助 最近碰到这样一个问题&#xff0c;在一张封面上直接显示书名&#xff0c;可能会存在书名看不太清楚的情况(容易受到背景干扰)&#xff0c;如下 为了解决这个问题&#xff0c;设计师提了一个“究极”方…

算法 - 希尔排序

原理 首先将数组两两分组&#xff0c;分成n组数组&#xff0c;每组数组内部进行排序。再分成n/2组数组&#xff0c;每组数组内部进行排序。直至分成只剩一组&#xff0c;最后进行排序得到最后的数组。 代码 public static int[] shell(int[] arr) {int temp;for (int gra …

计算机图形学13:三维图形的几何变换

作者&#xff1a;非妃是公主 专栏&#xff1a;《计算机图形学》 博客地址&#xff1a;https://blog.csdn.net/myf_666 个性签&#xff1a;顺境不惰&#xff0c;逆境不馁&#xff0c;以心制境&#xff0c;万事可成。——曾国藩 文章目录专栏推荐专栏系列文章序一、三维图形的几…

MongoDB综述【入门指南】

写这篇博客,正好是2023年4月5日15:29:31,是清明节,放假一天,我坐在我的小小租房室内,思考着没思考到啥,哈哈哈,感觉好着急啊!看完了一本《城南旧事》,但是就是不踏实,好吧~我来写一篇最近在学的一个技术 为了更优秀的自己~奥利给!! 首先,我们从最初级小白开始(因为自己也是小白…

卷麻了,00后测试用例写的比我还好,简直无地自容.....

前言 作为一个测试新人&#xff0c;刚开始接触测试&#xff0c;对于怎么写测试用例很头疼&#xff0c;无法接触需求&#xff0c;只能根据站在用户的角度去做测试&#xff0c;但是这样情况会导致不能全方位的测试APP&#xff0c;这种情况就需要一份测试用例了&#xff0c;但是不…