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函数的资料请关注脚本之家其它相关文章!
