likes
comments
collection
share

JavaScript高级--async/await详解

作者站长头像
站长
· 阅读数 14
  • 首先 async/await 是一种用于处理异步操作的语法糖,在 ES2017 被引入,用于更简洁地编写基于 Promise 的链式回调代码
  • 使用 async/await 主要是避免了传统的回调函数或 Promise 链的嵌套
  • 既然 async/await 是一种语法糖,那么其本质是什么呢?

以前的异步处理

  • 现在有一个 requestApi 函数,用于模拟网络请求,传入什么则返回什么
 function requestApi(params) {
   return new Promise((resolve, reject) => {
     setTimeout(() => {
       resolve(params);
     }, 1000);
   });
 }
  • 首先提一个需求,当第一次传入 Jimmy ,拿到请求返回的结果为 Jimmy;第二次需要利用第一次获得的结果去发送网络请求,拿到Jimmyaaa;而第三次再利用第二次的结果发送网络请求,拿到 Jimmyaaabbb
 params: 'Jimmy' ->> res1: 'Jimmy'
 params: res1 + 'aaa' ->> res2: 'Jimmyaaa'
 params: res2 + 'bbb' ->> res3: 'Jimmyaaabbb'
  • 在使用 async/await 的情况下,代码如下所示
 async function fetchData() {
   const res1 = await requestApi('Jimmy');
   const res2 = await requestApi(res1 + 'aaa');
   const res3 = await requestApi(res2 + 'bbb');
   console.log(res3); // Jimmyaaabbb
 }
 ​
 fetchData();
  • 假如没有 async/await 呢?那就需要 Promise 的链式调用了
 requestApi('Jimmy').then((res) => {
   requestApi(res + 'aaa').then((res) => {
     requestApi(res + 'bbb').then((res) => {
       console.log(res); // Jimmyaaabbb
     });
   });
 });

生成器结合异步

  • 如果使用 Promise 的链式调用,就会造成函数嵌套,形成回调地狱
  • 可以通过生成器结合 Promise 来尝试解决这个问题
 function* fetchData() {
   // 调用next(value1),yield的返回值被res1接收,res1 = 'Jimmy'
   const res1 = yield requestApi('Jimmy');
   // 调用next(value2),yield的返回值被res2接收,res1 = 'Jimmyaaa'
   const res2 = yield requestApi(res1 + 'aaa');
   // 调用next(value3),yield的返回值被res3接收,res3 = 'Jimmyaaabbb'
   const res3 = yield requestApi(res2 + 'bbb');
   // 输出'Jimmyaaabbb'
   console.log(res3);
 }
 ​
 const generator = fetchData(); // 首次调用函数不执行,而是返回生成器
 ​
 generator.next().value.then((value1) => {
   // 此时value1为第一个yield语句返回的promise结果,value1 = 'Jimmy'
   generator.next(value1).value.then((value2) => {
     // 此时value2为第二个yield语句返回的promise结果,value2 = 'Jimmyaaa'
     generator.next(value2).value.then((value3) => {
       // 此时value3为第三个yield语句返回的promise结果,value3 = 'Jimmyaaabbb'
       generator.next(value3);
     });
   });
 });

JavaScript高级--async/await详解

  • 尝试对上面代码进行封装,因为 generator.next() 返回的是对象(如 { value: promise, done: false } ),可以利用其 done 值判断来判断数据是否传输完成,结合递归进行优化
 // 该函数接收一个生成器函数为参数
 function execGenerator(GeneFun) {
   const generator = GeneFun();
 ​
   function exec(res) {
     const result = generator.next(res);
     if (result.done) return; // 如果result.done为true,则代表生成器函数执行完毕
     // 如果result.done为false,则代表生成器函数执行中
     result.value.then((res) => {
       // 继续调用exec,把下一个yield的值传入,知道result.done为true为止
       exec(res);
     });
   }
 ​
   exec();
 }
  • 那么可以将 fetchData 函数传给 execGenerator 处理,让其自动执行
  • 如果有其他类似的需求,也可以将请求的函数交由 execGenerator 进行处理
 // 传入一个生成器函数
 execGenerator(function* (){
   const res1 = yield requestApi('Jimmy');
   const res2 = yield requestApi(res1 + 'aaa');
   const res3 = yield requestApi(res2 + 'bbb');
   console.log(res3);
 }); 
 ​
 // 对比async/await
 async function fetchData() {
   const res1 = await requestApi('Jimmy');
   const res2 = await requestApi(res1 + 'aaa');
   const res3 = await requestApi(res2 + 'bbb');
   console.log(res3);
 }
  • 以上就是 async/await 语法糖的本质,其底层是生成器函数
  • 当一个函数被 async 标记,遇到 await 时函数则会等待,直到 await 后的代码处理完后才往后执行
  • 而生成器函数遇到 yield 语句时,函数也会暂停,待调用 next 之后才会继续往后执行
  • 也就是说,async/await 语法糖相当于自动化的生成器函数

async关键字

  • async 关键字用于声明一个异步函数,它是 asynchronous 单词的缩写,表示异步的

async 函数的多种写法

  • 直接在普通函数前加上 async 关键字
 async function foo() {}
  • 箭头函数前加上 async 关键字
 const bar = async () => {};
  • 类方法前加上 async 关键字
 class Foo {
   async bar() {}
 }

执行流程

  • 异步函数的内部代码执行过程和普通的函数是一致的,在没有特殊处理的情况下也是会被同步执行
 async function asyncFoo() {
   console.log('foo start');
   console.log('中间代码执行');
   console.log('foo end');
 }
 ​
 console.log('前面代码');
 asyncFoo(); // 这里不会异步执行,而是和普通函数一致
 console.log('后续代码');

异步函数有返回值时,和普通函数会有区别

  • 异步函数的返回值会被包裹到 Promise.resolve 中,所以其返回值一定是个 promise
 async function asyncBar() {
   ...
   return 123;
 }
 const promise = asyncBar();
 promise.then((res) => console.log('promise exec', res)); // promise exec 123
  • 如果异步函数返回 Promise,那么其本身 Promise.resolve 的状态会由返回的 Promise 决定
 async function asyncBar() {
   ...
   return new Promise((resolve, reject) => {
     reject('error')
   });
 }
 ​
 asyncBar().catch((err) => console.log('promise exec', err)); // promise exec error
  • 如果异步函数返回 thenable 对象,那么其本身 Promise.resolve 的状态由对象的 then 方法决定
 async function asyncBar() {
   ...
   return {
     then(resolve) {
       reject('reject');
     }
   };
 }
 ​
 asyncBar().catch((err) => console.log('promise exec', err)); // promise exec reject

抛出异常

  • 在普通函数中如果抛出了错误,那么该函数后的代码将不会执行
 function asyncBaz() {
   ...
   throw new Error('error');
 }
 ​
 asyncBaz();
 // 下面代码都不会执行
 console.log('后续代码');
  • 如果在 async 函数中抛出异常,程序并不像普通函数一样崩掉,而是会作为 Promisereject
 async function asyncBaz() {
   ...
   // 异步函数的异常,会作为异步函数返回的Promise的reject值
   throw new Error('error');
 }
 ​
 asyncBaz().catch(console.log); // Error: error ....
 // 下面代码照常执行,执行时机优先于catch回调
 console.log('后续代码');

await关键字

  • async 函数的特殊之处就是可以在内部使用 await 关键字,而普通函数中不可以

JavaScript高级--async/await详解

await 关键字的特点

  • 通常使用 await 后面会跟一个表达式,该表达式返回一个 Promise
 async function baz() {
   await 表达式(return Promise);
 }
  • await 会等到 Promise 的状态变成 fulfilled 后才继续执行后续代码,如果是 rejected 状态则不执行
 function requestApi() {
   return new Promise((resolve, reject) => {
     setTimeout(() => resolve('success'), 1000);
   });
 }
 async function baz() {
   const res = await requestApi(); // 待1秒之后得到结果,才会继续执行后续代码
   console.log(res); // success
 }
 ​
 baz();
  • await 等待期间,函数会呈现挂起状态,并将执行权交还给浏览器继续执行其他同步代码,待其他同步代码执行完成后,才会继续执行函数后续代码
 async function baz() {
   const res = await requestApi();
   console.log(res);
 }
 ​
 baz();
 console.log('后续代码');
 console.log('后续代码');

JavaScript高级--async/await详解

  • await 后面跟的是一个普通的值,那么会将该值立即返回,但是也不会影响函数的执行顺序,因为其内部会将普通值用 Promise.resolve 进行处理
 async function baz() {
   const res = await 123;
   console.log(res); // 123
 }
 ​
 baz();
 console.log('后续代码');
 console.log('后续代码');

JavaScript高级--async/await详解

  • await 后也可以是 thenable 对象,其返回的值是 then 方法中 resolve 的结果
 async function baz() {
   const res = await {
     then(resolve) {
       resolve('success');
     }
   };
   console.log(res); // success
 }
 ​
 baz();
 console.log('后续代码');
 console.log('后续代码');

JavaScript高级--async/await详解

转载自:https://juejin.cn/post/7281535380590559243
评论
请登录