深入学习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实例后,他会立即执行
如果还不明白,我把他打印出来:
从上图中可以看到 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是怎么进行链式调用的呢?
- promise中使用.then()的方法来进行链式调用,通过.then()的回调拿到上一个.then()的返回值
- 链式调用要求.then()方法返回的必须是一个promise,这样才有.then()方法;
- 需要按照顺序执行,所以只有当前的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的整个执行过程是:
- promise的构造方法接受一个executor(),在new Promise()时刻就立即执行了executor()回调,
- executor内部的异步任务被放入宏/微任务队列,等待执行。也就是上面代码中的定时器。
- 当then()被执行时,收集回调(成功或者失败) 同时放入成功或者失败队列。
- 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._resolveQueuelet _resolve = (val) => {// 从成功队列里取出回调依次执行while(this._resolveQueue.length) {const callback = this._resolveQueue.shift()callback(val)}}// 实现同resolvelet _reject = (val) => {while(this._rejectQueue.length) {const callback = this._rejectQueue.shift()callback(val)}}// new Promise()时立即执行executor,并传入resolve和rejectexecutor(_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+规范很多,核心规则有两个:
- Promise的状态有且只有三种: Pending(等待)。Fulfilled(已完成)。 Rejected(拒绝)。
- 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._resolveQueuelet _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)}}// 实现同resolvelet _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和rejectexecutor(_resolve, _reject)}// then方法,接收一个成功的回调和一个失败的回调then(resolveFn, rejectFn) {this._resolveQueue.push(resolveFn)this._rejectQueue.push(rejectFn)}
}
解释:
- 先初始化的时候增加三种状态
- 在进入constructor构造函数时把状态设置为PENDING
- 在执行_resolve 和_reject时需要判断当前状态是否为PENDING,如果不是直接return出去
- 在进入_resolve 和_reject流程后把状态对应改为FULFILLED 和REJECTED。
实现then方法
接下来到了 链式调用;上面我们提到 promsie采用了链式调用的方法来改变回调地狱
前面分析过链式调用的步骤:
- promise中使用.then()的方法来进行链式调用,通过.then()的回调拿到上一个.then()的返回值
- 链式调用要求.then()方法返回的必须是一个promise,这样才有.then()方法;
- 需要按照顺序执行,所以只有当前的promise状态变更后,才能执行下一个then方法
接下来就按照这个步骤实现then
注意: 在使用then方法的时候,我们需要判断 传入的参数是否是function;同时要判断promise的三种状态对应不同的写法
// then方法,接收一个成功的回调和一个失败的回调then(resolveFn, rejectFn) {// 根据规范,如果then的参数不是function,则我们需要忽略它, 让链式调用继续往下执行typeof resolveFn !== 'function' ? resolveFn = value => value : nulltypeof rejectFn !== 'function' ? rejectFn = reason => {throw new Error(reason instanceof Error? reason.message:reason);} : null// return一个新的promisereturn new MyPromise((resolve, reject) => {// 把resolveFn重新包装一下,再push进resolve执行队列,这是为了能够获取回调的返回值进行分类讨论const fulfilledFn = value => {try {// 执行第一个(当前的)Promise的成功回调,并获取返回值let x = resolveFn(value)// 分类讨论返回值,如果是Promise,那么等待Promise状态变更,否则直接resolvex 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._resolveQueuelet _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)}// 实现同resolvelet _reject = (val) => {const run = () => {if(this._status !== PENDING) return // 对应规范中的"状态只能由pending到fulfilled或rejected"this._status = REJECTED // 变更状态this._value = val // 储存当前valuewhile(this._rejectQueue.length) {const callback = this._rejectQueue.shift()callback(val)}}// 这里的定时器是为了 保证promise的执行顺序 下面的也是setTimeout(run)}// new Promise()时立即执行executor,并传入resolve和rejectexecutor(_resolve, _reject)}// then方法,接收一个成功的回调和一个失败的回调then(resolveFn, rejectFn) {// 根据规范,如果then的参数不是function,则我们需要忽略它, 让链式调用继续往下执行typeof resolveFn !== 'function' ? resolveFn = value => value : nulltypeof rejectFn !== 'function' ? rejectFn = reason => {throw new Error(reason instanceof Error? reason.message:reason);} : null// return一个新的promisereturn new MyPromise((resolve, reject) => {// 把resolveFn重新包装一下,再push进resolve执行队列,这是为了能够获取回调的返回值进行分类讨论const fulfilledFn = value => {try {// 执行第一个(当前的)Promise的成功回调,并获取返回值let x = resolveFn(value)// 分类讨论返回值,如果是Promise,那么等待Promise状态变更,否则直接resolvex 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结果传递给后面的Promisereason => 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 = 0let 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的状态变为rejectreject(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._resolveQueuelet _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)}// 实现同resolvelet _reject = (val) => {const run = () => {if(this._status !== PENDING) return // 对应规范中的"状态只能由pending到fulfilled或rejected"this._status = REJECTED // 变更状态this._value = val // 储存当前valuewhile(this._rejectQueue.length) {const callback = this._rejectQueue.shift()callback(val)}}setTimeout(run)}// new Promise()时立即执行executor,并传入resolve和rejectexecutor(_resolve, _reject)}// then方法,接收一个成功的回调和一个失败的回调then(resolveFn, rejectFn) {// 根据规范,如果then的参数不是function,则我们需要忽略它, 让链式调用继续往下执行typeof resolveFn !== 'function' ? resolveFn = value => value : nulltypeof rejectFn !== 'function' ? rejectFn = reason => {throw new Error(reason instanceof Error? reason.message:reason);} : null// return一个新的promisereturn new MyPromise((resolve, reject) => {// 把resolveFn重新包装一下,再push进resolve执行队列,这是为了能够获取回调的返回值进行分类讨论const fulfilledFn = value => {try {// 执行第一个(当前的)Promise的成功回调,并获取返回值let x = resolveFn(value)// 分类讨论返回值,如果是Promise,那么等待Promise状态变更,否则直接resolvex 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传递给后面的thenreason => 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 = 0let result = []return new MyPromise((resolve, reject) => {promiseArr.forEach((p, i) => {//Promise.resolve(p)用于处理传入值不为Promise的情况MyPromise.resolve(p).then(val => {index++result[i] = valif(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') //test1console.log(yield '2') //test2console.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的用法有很多相同的地方,都提供了暂停执行的功能,但是具体的不同有三点:
- async/await自带执行器 不需要手动next()就可以执行下一步
- async函数返回的值直接是一个promise对象,Genreator返回的是生成器对象
- 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)) //1console.log(yield Promise.resolve(2)) //2console.log(yield Promise.resolve(3)) //3
}run(myGenerator)
返回Promise和异常处理
这两个也是上面提到的特点;
返回promise:这里我们可以把上述代码中的(gen().next.value)
都用Promise.resolve()转化一遍
异常处理:如果promise执行失败,导致后续的执行中断,采用Generator.prototype.throw()
来抛出错误,这样能被外层的try-catch捕获到
所以这里我们改造一下上面一段代码:
function run(gen) {//把返回值包装成promisereturn 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) //2console.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教程
相关文章:

深入学习JavaScript系列(七)——Promise async/await generator
本篇属于本系列第七篇 第一篇:#深入学习JavaScript系列(一)—— ES6中的JS执行上下文 第二篇:# 深入学习JavaScript系列(二)——作用域和作用域链 第三篇:# 深入学习JavaScript系列ÿ…...

Mybatis中的Map的使用和模糊查询的需求实现及其防SQL注入优化
文章目录一.Map的使用和模糊查询的需求实现及其防SQL注入优化1.1 Map的使用1.2 模糊查询的实现1.2.1 防SQL注入优化1.2.2 总结一.Map的使用和模糊查询的需求实现及其防SQL注入优化 1.1 Map的使用 替换之前的根据ID查询信息: 1.编写接口: User getUse…...

【redis】redis缓存更新策略
目录一、缓存更新策略二、主动更新策略三、Cache Aside Pattern3.1 删除缓存还是更新缓存?3.2 如何保证缓存与数据库的操作同时成功或失败?3.3 先操作缓存还是先操作数据库3.3.1 先删缓存再删库3.3.2 先删库再删缓存一、缓存更新策略 1.内存淘汰:不用自…...

LeetCode刷题--复制带随机指针的链表
复制带随机指针的链表1.题目2.解题思路3.完整代码1.题目 题目链接: https://leetcode.cn/problems/copy-list-with-random-pointer/ 给你一个长度为 n 的链表,每个节点包含一个额外增加的随机指针 random ,该指针可以指向链表中的任何节点或空节点。 …...

关于我的第一台电脑 华硕
2011年买的,第一台电脑是华硕 U36KI243SD 13.3英寸 白色 i5 1G独显 USB3.0 500G 当时花了5699,着实是一笔巨款,我同学看了一眼就说“我C,这本真好”。 买它主要还是因为好看。当时win7也才开始流行,感觉用上这个本&…...

【华为OD机试 2023最新 】 最大化控制资源成本(C++ 100%)
文章目录 题目描述输入描述输出描述备注用例题目解析C++题目描述 公司创新实验室正在研究如何最小化资源成本,最大化资源利用率,请你设计算法帮他们解决一个任务混部问题: 有taskNum项任务,每个任务有开始时间(startTime),结束时间(endTime),并行度(parallelism)…...

leetcode 有序数组的平方(977)
题目 给你一个按 非递减顺序 排序的整数数组 nums,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。 示例 1: 输入:nums [-4,-1,0,3,10] 输出:[0,1,9,16,100] 解释:平方后,数组变…...

文本三剑客之awk
文本三剑客之awkawk命令的简要处理流程awk命令的执行过程NR输出分割符和输入分割符案例awk命令引用shell变量awk的几个内置函数流控数组awk命令的简要处理流程 awk命令的执行过程 awk BEGIN{commands} pattern{commands} END{commands}files 执行BEGIN {commands}语句块中的语…...

RK3568平台开发系列讲解(驱动基础篇)IS_ERR函数的使用
🚀返回专栏总目录 文章目录 一、IS_ERR函数二、内核错误码沉淀、分享、成长,让自己和他人都能有所收获!😄 📢本篇将介绍 IS_ERR 函数的使用。 一、IS_ERR函数 对于任何一个指针来说,必然存在三种情况: 一种是合法指针一种是 NULL (也就是空指针)一种是错误指针(也就…...

特殊的类之注解
注解🚙注解的入门和作用以及原理示例注解的方法名就是属性名Retention的作用Target的作用注解的属性设置默认值天生我材必有用,千金散尽还复来。——唐代李白《将进酒》 在Java中,注解实际上是特殊类型的接口,当我们使用注解时&am…...

商业分享:盲盒电商开启电商新可能
盲盒,顾名思义,一个看不出里面装着什么东西的盒子。当你看不见盲盒里的商品时,你会思考盲盒里可能装着什么,它会诱发你的好奇心,而在好奇心的促使下,你会不由自主地买一个拆开来看,刚好大多数盲…...

【计算机架构】如何计算 CPU 时间
目录 0x00 响应时间和吞吐量(Response Time and Throughput) 0x01 相对性能(Relative Performance) 0x02 执行时间测量(Measuring Execution Time) 0x03 CPU 时钟(Clocking) 0x…...

银行数字化转型导师坚鹏:银行行长如何进行数字化转型
银行行长如何进行数字化转型 ——数字化转型背景下重塑银行行长核心竞争力 授课背景: 很多银行存在以下问题:银行行长不知道如何进行数字化转型?银行行长不清楚银行数字化能力模型的内涵?银行行长不知道如何通过数字化转型提…...

N32G45x学习笔记--- gpio引脚复用
关于gpio的引脚复用需要开启复用时钟 RCC_EnableAPB2PeriphClk(RCC_APB2_PERIPH_AFIO, ENABLE);官方引脚复用: 芯片上电默认使能 SWD-JTAG 调试接口,调试接口被映射到 GPIO 端口上 禁止 JTAG 使能SWJ-DP /* 禁止 JTAG 使能SWJ-DP */GPIO_ConfigPinRemap(GPIO_RMP_SW_JTAG_S…...

ArcGIS Pro中使用深度学习的高分辨率土地覆盖制图
本文非常详细的讲解了利用深度学习在高分辨率土地覆盖制图的应用,本文作者:Amin Tayyebi,文章从数据准备到训练U-Net模型等等细节都有讲解。本译文只是使用谷歌翻译而成。文章可能有错误语句及不通顺情况,所以仅供参考学习。有需要…...

【学习笔记】「NOI2018」冒泡排序
从题解的角度来说,这是一道简单题。不过考场上在没有任何人提示的情况下要想出正确的结论其实并不容易。 我自己做这道题的时候,因为没有想清楚题目给出的下界能取到的充要条件是什么,所以到了很晚才猜到结论,以至于难以为继。 …...

【Ruby学习笔记】3.Ruby 语法及数据类型
前言 本章介绍Ruby的语法和数据类型。 Ruby 语法 让我们编写一个简单的 Ruby 程序。所有的 Ruby 文件扩展名都是 .rb。所以,把下面的源代码放在 test.rb 文件中。 实例 #!/usr/bin/ruby -wputs "Hello, Ruby!";在这里,假设您的 /usr/bin …...

华为OD机试题【字符匹配】用 Java 解 | 含解题说明
华为Od必看系列 华为OD机试 全流程解析+经验分享,题型分享,防作弊指南华为od机试,独家整理 已参加机试人员的实战技巧华为od 2023 | 什么是华为od,od 薪资待遇,od机试题清单华为OD机试真题大全,用 Python 解华为机试题 | 机试宝典本篇题目:字符匹配 题目 给你一个字符串…...

JavaScript数组对象的浅拷贝与深拷贝(二)实现对象深拷贝的方法(5种)
JavaScript实现对象深拷贝的方法(5种)知识回调(不懂就看这儿!)场景复现实现对象深拷贝的五种方法1.json暴力转化2.es6扩展运算符3.for in循环遍历对象4.Object.assign()对象的合并5.利用循环和递归的方式实现对象浅拷贝…...

iPhone屏幕适配(之屏幕尺寸)
Device screen size 各设备屏幕尺寸 DeviceDimensions (portrait)iPhone 14 Pro Max430x932 pt (1290x2796 px 3x)iPhone 14 Pro393x852 pt (1179x2556 px 3x)iPhone 14 Plus428x926 pt (1284x2778 px 3x)iPhone 14390x844 pt (1170x2532 px 3x)iPhone 13 Pro Max428x926 pt (…...

手机变砖修复神器之 8 个的 Android手机系统修复工具
如果您经常在 Android 设备上遇到问题,则需要找到最好的 Android 系统修复应用程序并使用它来一劳永逸地解决您的问题。如果您不确定执行此操作的好应用是什么,我们在这里为您列出了一些最好的 Android 修复软件。 虽然现在出货的 Android 手机相当稳定…...

稀疏矩阵(Sparse Matrix)
1.背景 在数据科学和深度学习等领域常会采用矩阵格式来存储数据,但当矩阵较为庞大且非零元素较少时, 如果依然使用dense的矩阵进行存储和计算将是极其低效且耗费资源的。所以,通常我们采用Sparse稀疏矩阵的方式来存储矩阵,提高存储…...

深度学习中的损失函数
文章目录一. Loss函数1. 均方差损失(Mean Squared Error Loss)2. 平均绝对误差损失(Mean Absolute Error Loss)3.(Huber Loss)4. 分位数损失(Quantile Loss)5. 交叉熵损失࿰…...

English Learning - L2 语音作业打卡 辅音咬舌音 [θ] [ð] Day29 2023.3.21 周二
English Learning - L2 语音作业打卡 辅音咬舌音 [θ] [] Day29 2023.3.21 周二💌发音小贴士:💌当日目标音发音规则/技巧:🍭 Part 1【热身练习】🍭 Part2【练习内容】🍭【练习感受】🍓元音 [θ]…...

【原始者-综述】
目录知识框架No.1 AcwingNo.2 LeetcodeNo.3 PTANo.4 蓝桥No.5 牛客网No.6 代码随想录知识框架 No.1 Acwing 那就点击这里转向自己的Acwing题解咯 单调栈,动态规划,贪心,回溯,二叉树,站与队列,双指针&#…...

C++内存模型
目录 一.内存分区 二,分区顺序 1 程序运行前 2 程序运行后 3.new操作符 一.内存分区 内存分区意义:不同区域存放的数据,赋予不同的生命周期, 给我们更大的灵活编程 内存可以分为以下几个区: 代码区:存放函数体的二进制代码…...

八股+面经
文章目录项目介绍Java基础MapHashMap v.s Hashtable(5点)ConcurrentHashMap v.s Hashtable(2点)代理模式1. 静态代理2. 动态代理2.1 JDK 动态代理机制2.2 CGLIB 动态代理机制Java并发线程volatilesynchronized线程池JVM类加载机制垃圾回收(GC)1. 引用类型…...

MySQL更新数据流程
1.mysql三种重要日志 redo log(重做日志):存在于引擎层,物理存储,通过设置innodb_flush_log_at_trx_xommit1 让其持久化到磁盘,保证引擎的crash-safe能力,遵从WAL技术(Write-Ahead …...

测试开发进阶系列课程
测试开发系列课程1.完善程序思维--------案列:图书管理系统的创建**(一)图书管理系统的创建**1.完善程序思维--------案列:图书管理系统的创建 (一)图书管理系统的创建 1.在main中写入主函数,…...

Qt源码阅读(三) 对象树管理
对象树管理 个人经验总结,如有错误或遗漏,欢迎各位大佬指正 😃 文章目录对象树管理设置父对象的作用设置父对象(setParent)完整源码片段分析对象的删除夹带私货时间设置父对象的作用 众所周知,Qt中,有为对象设置父对象…...