【js】async函数与其他异步操作

1.其他异步操作

  1. 传统的异步操作
  2. Generator的异步操作
异步:任务的不连续执行,跳跃执行,在一个任务阻塞时主线程去执行其他任务,阻塞任务接收到执行信号时,再重新添加到任务队列中执行
1.1传统的异步操作 传统的异步操作
  1. 回调函数
  2. 事件监听
  3. 发布/订阅
  4. Promise对象
1.1.1回调函数(callback)
  • 将一个任务分成多段,嵌套的形式去执行
    fs.readFile('/etc/passwd' , 'utf-8', function(err , data){ if (err) throw err; console.log(data); });

    readFile的第三个参数就是一个回调函数,在请求文件成功后才会调用
    回调函数可以实现程序的异步执行
回调地狱callback hell
//不同的任务 function toEat(val, fn) { setTimeout(() => { fn(val) }, 2000); } function toDrink(val, fn) { setTimeout(() => { fn(val) }, 1000); }function toKTV(val, fn) { setTimeout(() => { fn(val) }, 500); } //按顺序执行任务 toDrink("吃火锅", data => { console.log(data); toDrink("喝啤酒", data => { console.log(data); toKTV("唱歌", data => { console.log(data); }) }) }) //"吃火锅" //"喝啤酒" //"唱歌"

在一个任务的回调函数中嵌套其他任务
其他任务也有回调函数,这样的层层嵌套当出现错误时代码很难维护
1.1.2 Promise
//任务封装Promise function toResolve(data, delay) { const promise = new Promise((resolve, reject) => { setTimeout(() => { resolve(data) }, delay); }) return promise } //不同的任务 function toEat(val) { return toResolve(val, 2000) } function toDrink(val) { return toResolve(val, 1000) } function toKTV(val) { return toResolve(val, 500) } //按顺序执行任务 toEat("吃火锅").then(val => { console.log(val); return toDrink("喝啤酒") }).then(val => { console.log(val); return toKTV("唱歌") }).then(val => { console.log(val); }).catch(res => { console.log(res); }) //吃火锅 //喝啤酒 //唱歌

定时执行回调函数改为定时改变Promise状态
结构更加清晰
但是由于原任务的Promise封装,代码冗余,由于then的堆积,语义变得不清楚
1.2.Generator的异步操作 仍然使用上面封装的Promise任务,使用Generator去异步执行
//生成器函数 function* asyncJob() { let val1 = yield toEat("吃火锅") console.log(val1); let val2 = yield toDrink("喝啤酒") console.log(val2); let val3 = yield toKTV("唱歌") console.log(val3); } //生成器函数的执行器 function runGenerator(fn) { let iter = fn() let { value, done } = iter.next() value.then(val1 => { let { value, done } = iter.next(val1) value.then(val2 => { let { value, done } = iter.next(val2) value.then(val3 => { iter.next(val3) }) }) }) } //开始执行异步任务 runGenerator(asyncJob) //吃火锅 //喝啤酒 //唱歌

以toEat为例,观察异步任务如何执行的
  1. 调用生成器函数的执行器
  2. 调用生成器函数,返回迭代器对象
  3. 第一次调用迭代器上的next方法:执行至第一个yild,调用toEat函数,返回一个Promise对象作为next返回值的value属性,解构取value的值
  4. 在value上调用then:接收resolve中的值(会延迟2s),通过第二次调用next将接收的值赋值给第一个yield
  5. 可以在第一个yield和第二个yield之间处理toEat返回的值
  6. 对于第二个next的返回值可以解构进行同样的操作
重点
调动next后发生了什么
next参数传递的原则
Promise的处理与状态变化
2.async函数 Generator的语法糖 - 使异步操作更加简单
使用上面封装的Promise任务
//生成器的异步任务 async function asyncJob() { let val1 = await toEat("吃火锅") console.log(val1); let val2 = await toDrink("喝啤酒") console.log(val2); let val3 = await toKTV("唱歌") console.log(val3); } //开始执行异步任务 asyncJob() //吃火锅 //喝啤酒 //唱歌

async替代了生成器中 星号*,用 await替代生成器中的 yield
与生成器的异步操作相比,async函数不用手写执行器, 自带执行器
直接调用async函数就可以执行内部的任务
async函数对Generator的改进
  • 内置执行器
    Generator函数的执行需要执行器,手写或者引入co模块,而async函数自带执行器,可以让其像普通函数一样执行
  • 更好的语义
    async与await比星号*与yield的语义表达更清晰
  • 更广的适用性
    co模块指定yield后只能是Thunk函数Promise对象,而async函数的await后可以是Promise对象原始类型的值
  • 【【js】async函数与其他异步操作】返回值是Promise
    Genetator的返回值是迭代器对象,async函数返回的是一个Promise对象
    async函数中有 return时,默认return的值Promise.resolve()转成Promise对象,在外部用then可以接收
2.1 async函数的错误处理 已知async返回的是一个Promise,可以在外部捕获错误
//生成器的异步任务 async function asyncJob() { let val1 = await toEat("吃火锅").then(() => { throw new Error("饭店倒闭了") }) console.log(val1); let val2 = await toDrink("喝啤酒") console.log(val2); let val3 = await toKTV("唱歌") console.log(val3); }//开始执行异步任务 asyncJob().catch(res => { console.log(res); }) // Error:饭店倒闭了

一条await后的语句出错时,后续不再执行,直接将错误抛出
try...catch
//生成器的异步任务 async function asyncJob() { try { let val1 = await toEat("吃火锅").then(() => { throw new Error("饭店倒闭了") }) console.log(val1); } catch (e) { console.log(e); } try { let val2 = await toDrink("喝啤酒") console.log(val2); let val3 = await toKTV("唱歌") console.log(val3); } catch (e) { console.log(e); }} //开始执行异步任务 asyncJob().catch(res => { console.log(res); }) // Error:饭店倒闭了 // 喝啤酒 // 唱歌

互相无影响的任务放在不同的try...catch代码块中
互相有影响的任务放入相同的try...catch代码块中
2.2 async函数中的并发
//生成器的异步任务 async function asyncJob() {let val = await Promise.all([toEat("吃火锅"), toDrink("喝啤酒")]) console.log(val); let val3 = await toKTV("唱歌") console.log(val3); } //开始执行异步任务 asyncJob().catch(res => { console.log(res); }) // ['吃火锅','喝啤酒'] // "唱歌"

同样也可以使用Promise.any() Promise.race()等方法

    推荐阅读