JS中Generator函数与async函数用法介绍
作者:剪刀石头布啊
Generator函数
Generator
函数看起来像指针函数,实际上跟我们的普通函数差不多,有两个特征
1、function
关键字与函数名之间有一个星号
2、函数体内部使用yield
表达式,定义不同的内部状态(yield
在英语里的意思就是“产出”),这里有暂停、等待执行的意思
Generator
函数 运行后跟普通函数一样,只不过碰到 yield
关键字会暂停
,需要等待调用next
之后才会继续
往后执行,直到遇到下一个 yield
或 return
yield
作为表达式
一项时,需要使用 ()
括起来,否则会报错,例如: 2 * (yield x)
next
标识着 generator
函数开始往后执行语句,默认执行到 下一个 yield
语句,并返回 yield 后的内容
到 value
中,没有遇到 return
或者函数结束
,done
参数为false
,否则done
参数为 true
return
标识着函数的结束,这里也不例外,只不过结束时返回的内容也可以被 next
返回; 另外,不写 return
则与return undefined
类似
可以参考下面案例理解
function* generator() { yield 1 yield 2 yield 3 yield 4 return 5 // { value: 5, done: true },再往后 next 就和不写一样了 // 不写return,则与return undefined 一样 返回 { value: undefined, done: true } } //获取 generator 遍历器 let g = generator() let next = null do { next = g.next() //开始往后执行到下一个 yield 语句,并返回 console.log(next) }while(next?.done === false) //执行到 return 或 函数结束 则停止打印 next = g.next() console.log(next) // { value: 1, done: false } // { value: 2, done: false } // { value: 3, done: false } // { value: 4, done: false } // 有return 5 打印 { value: 5, done: true }, 没有则打印 { value: undefined, done: true }
如果在 class
中怎么表示呢,里面是构造函数的包装,写法不一样, 只需要在前面加上一个*
即可
class A { //前面加上 * 即可 * generator() { yield 1 yield 2 } } let a = new A() let g = a.generator() let next = g.next() console.log(next) next = g.next() console.log(next)
generator 与 Iterator遍历器
从上面打印就可以看出,generator函数
,实际上返回的是一个 Iterator遍历器
,因此我们亦可以通过遍历器的手段来执行 generator遍历器
下面使用 for ... of
来遍历一下我们上面的 generator遍历器
//从上面就可以看出,generator函数 返回了一个 Iterator遍历器对象 //我们遍历一次试试 let g = generator() for (let item of g) { console.log(item) } //结果打印 1 2 3 4
看其结果发现没有打印最后的return
,了解遍历器就会知道,遍历器遇到 done
为true
时会直接结束(作为遍历器使用的话,不要在 return 语句
放遍历内容)
我们再对比一下看看我们的 generator遍历器
是否真的是 Iteraotr遍历器
,发现一模一样
//g[Symbol.iterator]() === g //遍历器属性一致,返回为 true,不信打印遍历器属性比较试试
next
前面介绍了,generator遍历器
调用 next()
,会继续执行到下一个 yield语句
此外,next
可以传参
,我们传递的参数
会作为 上一个 yield 返回的结果
,没有传参,默认返回都是 undefined
注意: yield
返回的结果不是
该表达式后面的内容,且 yield 作为表达式一项时,需要使用 () 括起来,否则会报错
下面看一下案例就知道结果了(过程已经标出)
//包含表达式时,yield 语句需要用括号括起来,否则会报错 function* generator() { let num1 = yield 1 console.log('num1', num1) let num2 = num1 * (yield 2) console.log('num2', num2) yield 3 yield 4 } //next传入的值,作为上一个 yield 的返回值使用 let g = generator() let next = g.next() //此时执行到 yield 1,并包装返回 yield 1 执行的结果 console.log(next) //{ value: 1, done: false } //传入 2, 当做上一个语句的 yield 1 的返回值(不传接收到的值均为undefined) //即:返回 2 赋值给 num,然后执行到 (yield 2),并包装返回 (yield 2) 的结果 next = g.next(2) //执行完毕后,打印:num1 2 console.log(next) //{ value: 2, done: false } //传入 3. 当做上一个语句的 (yield 2) 的返回值(不传接收到的值均为undefined) //即:num2 = num1 * 3, 然后执行到 yield 3,并包装返回 yield 3 的结果 next = g.next(3) //执行完毕后,打印:num2 6 console.log(next) //{ value: 3, done: false }
由上述可以可以看到 next 传参
挺好用的,除了上述,甚至可以使用传递的参数,用于跳出死循环......
throw
这里的throw
为通过 generator
遍历器抛出的异常,其异常会抛出到 generator 函数里面
,因此需要注意抛出异常的位置
,才能更好地配合try...catch
使用
//throw() // 相当于在 generator 里面抛出了一个异常 function* generator() { try { yield 1 }catch(err) { console.log(err) } yield 2 yield 3 yield 4 } let g = generator() let next = g.next() console.log(next) //执行完毕 yield 1后,随后在 yield 1 ~ yield 2 之间抛出异常,因此,try...catch 包裹主 yield 1 才行 let t = g.throw('啦啦啦') // 抛出异常并执行到下一个 yield,如果没处理错误,程序异常 console.log(t) next = g.next() console.log(next)
return
指定 return
会理立即结束 generator 函数
,并返回 { value: undefined, done: true }
,上面有介绍
function* generator() { yield 1 yield 2 yield 3 yield 4 } let g = generator() next = g.next() next = g.return() //直接返回 { value: undefined, done: true },如果传参则返回return传递的参数 console.log(next) //会直接结束
另外,当存在 try...finally
代码块时,并且执行到 try
里面时return
,会仍然执行finally
里面的语句,且会执行到finally里面
的 yield
语句,finally
执行完毕,才是真正结束,并且return
带入的参数,会应用到 finally
语句的末尾
function* generator() { yield 1 try { //执行到这里,外面 return 的话,也需要走完 finally 才会结束 yield 2 yield 3 }finally { yield 7 yield 8 } yield 4 } let g1 = generator() next = g1.next() // { value: 1, done: false } next = g1.next() // { value: 2, done: false } next = g1.return(100) // { value: 7, done: false },return 后返回下一个,为finally中的 yield 7,这里顺道返回一个值测试一下最后的返回值 next = g1.next() // { value: 8, done: false } next = g1.next() // { value: 100, done: true } next = g1.next() // { value: undefined, done: true }
yield*
yield*
看着像个指针,其指向一个 generator遍历器
,会自动展开,不多说
//yield* 会展开 generator 函数 function* gen1() { yield 1 yield 2 } function* gen2() { yield 0 yield* gen1() } //相当于下面展开的语句,可以看出yield语句总是一个一个执行的,即使嵌套也不会一次执行一堆 function* gen3() { yield 0 yield 1 yield 2 }
generator应用
generator
应用很多,这里简单介绍两种,算是小试牛刀
正常切换开关,需要状态,这里通过 generator 遍历器,避免了新增开关状态(不适用于多方控制,例如:需要与后台远程同步状态)
//切换状态 function* toggleSwitch() { while(true) { console.log('开灯') yield 1 console.log('关灯') yield 0 } } //是不是切换很简单了 let t = toggleSwitch() let next = t.next() //开灯 console.log(next) next = t.next() //关灯 console.log(next)
流程化管理,外部不用关心内部(例如:加工软件,不同工种对于自己的操作工序,完成后只需要点击一下 next 即可)
function* order() { yield '收到订单' yield '加工' yield '送检' yield '质检' yield '发货' yield '完成订单' } let o = order() let next = o.next() //收到订单 next = o.next() //加工 next = o.next() //送检 next = o.next() //质检 next = o.next() //发货 next = o.next() //完成订单
async、await函数
async、await
,我们平时用的比较多,这里简单介绍一下
async、await
就是根据 generator函数
改造而成,其改进了 generator函数
作为普通函数痛点,其加入了执行器
,能像普通函数一样直接执行,更加方便
,且语义更加清晰
,结果返回一个 Promise
(可以看出使用Promise包装而成)
简而言之:async
声明一个异步函数,await
等待执行,当await
语句执行完毕后,才会执行后面语句(出现错误直接抛出错误结束,不往后执行),并且如果没有 await
的存在,async
函数,跟一个普通的同步函数执行顺序没有什么区别
ps: 关于 promise
前面有介绍,await
执行的过程与其一样,实际上会马上执行任务队列下一个任务,前面的任务执行完毕后,才会执行到 await 后面的语句
简单做一个使用案例,
async function a() { await promise1... await promise2... // 如果不return自己内容,返回的则是 undefind return 1 } function cc() { a().then(res => { console.log(res) }).catch(err => { console.log(err) }) }
你可能会想,如果第一个 promise 出错了怎么办,第二个会执行么?
答案:不会,第一个出错后,会抛出一个异常,此时会结束解释器的执行,直接反馈错误到外部
下面改进上一个案例
function a1() { return Promise.reject('出错了 ') } function a2() { return Promise.resolve('成功了 ') } async function a() { await a1() console.log(11111111111) await a2() // 如果不return自己内容,返回的则是 undefind return 1 } function cc() { a().then(res => { console.log(res) }).catch(err => { console.log(err) }) } //结果是没有执行打印 11111111111,且执行到了 .catch 中
下面我们模拟传递多个图片的案例,可以看出 async、await
应用多么方便
function uploadAImage(fileUrl) { return new Promise(function(resolve, reject) { setTimeout(() => { let res = fileUrl + '成功了' console.log(res) resolve(res) }, 500); }) } async function uploadImages() { //准备多个图片url let fileUrl = ['url1', 'url2', 'url3'] for (url of fileUrl) { try { await uploadAImage(url) }catch(err) { console.log(err) //实际上更复杂,成功的下次就不需要在上传了 return Promise.reject('存在上传失败的内容') } } return '都成功了' } uploadImages().then(res => { console.log(res) }).catch(err => { console.log(err) })
async、await 函数使用简单就先介绍到这里了,后面会对 generator 进行对比,是怎么改造了的
generator 模仿 async 自动执行
前面说了 async
函数 是通过 generator
函数 改造而来的,里面添加了执行器,我们自己尝试一下做一个简易的generator
执行器
这里是一个async 的默认执行案例
//使用 generator + 执行器,简单翻译一下 async、await async function asyncDefaultFunction() { let res = await new Promise((resolve) => { setTimeout(() => { resolve(1) }, 1000); // setTimeout(() => { // reject('啦啦啦') // }, 1000); }) return res } asyncDefaultFunction().then(res => { console.log(res) })
我们使用 generator
改造成 async
自动执行的模式
//使用 async 之后,实际上我们将返回函数包装一下即可,并且将 yield 就是 await function asyncFunction() { return co(function* () { //async 函数里面的内容,将 yield 与 await 替换即可 let res = yield new Promise((resolve, reject) => { setTimeout(() => { resolve(1) }, 1000); // setTimeout(() => { // reject('啦啦啦') // }, 1000); }) return res }) } //编写一个自动执行 generator 的 co 函数 function co(genFunc) { return new Promise(function (resolve, reject) { const g = genFunc() //获取generator,准备执行 function nextFunc(value) { let next; try { next = g.next(value) }catch(err) { return reject(err) } if (next.done) { return resolve(next.value) } //为什么要promise包装,语句可能为同步函数,可能为异步函数,木事保证正确执行 //如果为 promise 直接原封不动返回,如果为普通对象则包装,具体见promise Promise.resolve(next.value).then(function(res) { nextFunc(res) }, function(err) { reject(err) }) } nextFunc(undefined) }) } asyncFunction().then(res => { console.log(res) }).catch(err => { console.log(err) })
这样就实现了一个简易的 generator 自动执行器
同时我们也发现了,async
与我们普通函数相比,加剧了负担
,为了优化性能,我们平时如果没有用到异步函数(或者连 await
都用不到的),将多余的 async
去掉吧,这样某种程度上能够避免性能浪费,尤其是到了循环语句
最后
看到了这里,相信我们也能学习到不少东西,可以想一下,其他语言是不是也是类似这样呢,遇到了相似的情况我们是不是也豁然开朗了呢,很多语言是互通的,学习就是积累的过程,希望我们此次能有所收获!
以上就是JS中Generator函数与async函数用法介绍的详细内容,更多关于JS Generator函数与async函数的资料请关注脚本之家其它相关文章!