JS异步编程——深刻理解async/await

在作项目的时候,常常会碰到关于异步的问题,遇到多个异步请求,又要控制其顺序,该怎么办?涉及多个回调造成回调地狱又该如何处理?ES2017 标准引入了 async 函数,使得异步操做变得更加方便。本文主要从async/await的基本用法、平行任务、注意事项几个方面来介绍。javascript

0.传统js的异步编程方法

ES6 诞生之前,异步编程的方法,大概有下面四种。html

  • 回调函数
  • 事件监听
  • 发布/订阅
  • promise对象

1.基本用法

首先咱们经过一个例子,分别使用async/await与传统异步方法进行编写,进而对async/await写法有个初步的了解。java

先定义一个 Fetch 方法用于获取信息:git

function fetchUser(){
    return new Promise((resolve, reject) => {
        fetch('https://whuzxq.com/userList/4356')
        .then((data) => {
            resolve(data.json());
        }, (error) => {
            reject(error);
        })
    });
}
复制代码

Promise方式es6

function getuserByPromise(){
    fetchUser()
        .then((data) => {
            console.log(data);
        }, (error) => {
            console.log(error);
        })

}
复制代码

Promise的方式虽然解决了回调地狱,可是整段代码充满then,语义化不明显,代码流程不能很好的表示执行流程。github

async 方式编程

/** * async 方式 */
 async function getUserByAsync(){
     let user = await fetchUser();
     return user;
 }
getUserByAsync()
.then(v => console.log(v));
复制代码

async 函数完美的解决了上面两种方式的问题。同时 async 函数自带执行器,执行的时候无需手动加载。json

经过以上两个例子,应该对async的使用有了一个初步的认识,下文将详细列出async/await相关的重要知识点。promise

async

1.async函数返回一个promise对象。并发

2.async函数内部return语句返回的值,会成为then方法回调函数的参数。

async function f() {
  return 'hello world';
}

f().then(v => console.log(v))
复制代码

3.async函数返回的 Promise 对象,必须等到内部全部await命令后面的 Promise 对象执行完,才会发生状态改变,除非遇到return语句或者抛出错误。也就是说,只有async函数内部的异步操做执行完,才会执行then方法指定的回调函数。

async function getTitle(url) {
  let response = await fetch(url);
  let html = await response.text();
  return html.match(/<title>([\s\S]+)<\/title>/i)[1];
}
getTitle('https://tc39.github.io/ecma262/').then(console.log)
// "ECMAScript 2017 Language Specification"
复制代码

上面代码中,函数getTitle内部有三个操做:抓取网页、取出文本、匹配页面标题。只有这三个操做所有完成,才会执行then方法里面的console.log。

await命令

须要理解如下要点: 1.正常状况下,await命令后面是一个 Promise 对象。若是不是,会被转成一个当即resolve的 Promise 对象。

async function f() {
  return await 123;
}

f().then(v => console.log(v))
// 123
复制代码

2.只要一个await语句后面的 Promise 变为reject,那么整个async函数都会中断执行。

async function f() {
  await Promise.reject('出错了');
  await Promise.resolve('hello world'); // 不会执行
}
复制代码

有时,咱们但愿即便前一个异步操做失败,也不要中断后面的异步操做。这时能够将第一个await放在try...catch结构里面,这样无论这个异步操做是否成功,第二个await都会执行。

async function f() {
  try {
    await Promise.reject('出错了');
  } catch(e) {
  }
  return await Promise.resolve('hello world');
}

f()
.then(v => console.log(v))
// hello world
复制代码

另外一种方法是await后面的 Promise 对象再跟一个catch方法,处理前面可能出现的错误。

async function f() {
  await Promise.reject('出错了')
    .catch(e => console.log(e));
  return await Promise.resolve('hello world');
}

f()
.then(v => console.log(v))
// 出错了
// hello world
复制代码

注意事项

1.前面已经说过,await命令后面的Promise对象,运行结果多是rejected,因此最好把await命令放在try...catch代码块中。

2.await命令只能用在async函数之中,若是用在普通函数,就会报错。

2.等待平行任务

进行 JavaScript 异步编程时,你们常常须要逐一编写多个复杂语句的代码,并都在调用语句前标注了 await。因为大多数状况下,一个语句并不依赖于前一个语句,可是你仍不得不等前一个语句完成,这会致使性能问题。所以,多个await命令后面的异步操做,若是不存在继发关系,最好让它们同时触发。

let foo = await getFoo();
let bar = await getBar();
复制代码

上面代码中,getFoo和getBar是两个独立的异步操做(即互不依赖),被写成继发关系。这样比较耗时,由于只有getFoo完成之后,才会执行getBar,彻底可让它们同时触发。

// 写法一
let [foo, bar] = await Promise.all([getFoo(), getBar()]);

// 写法二
let fooPromise = getFoo();
let barPromise = getBar();
let foo = await fooPromise;
let bar = await barPromise;

复制代码

3.实例

实例一:假定某个 DOM 元素上面,部署了一系列的动画,前一个动画结束,才能开始后一个。若是当中有一个动画出错,就再也不往下执行,返回上一个成功执行的动画的返回值。

async function chainAnimationsAsync(elem, animations) {
  let ret = null;//变量ret用来保存上一个动画的返回值
  try {
    for(let anim of animations) {
      ret = await anim(elem);
    }
  } catch(e) {
    /* 忽略错误,继续执行 */
  }
  return ret;
}
复制代码

实例二:实际开发中,常常遇到一组异步操做,须要按照顺序完成。好比,依次远程读取一组 URL,而后按照读取的顺序输出结果。

async function logInOrder(urls) {
  for (const url of urls) {
    const response = await fetch(url);
    console.log(await response.text());
  }
}
复制代码

上面代码确实大大简化,问题是全部远程操做都是继发。只有前一个 URL 返回结果,才会去读取下一个 URL,这样作效率不好,很是浪费时间。咱们须要的是并发发出远程请求。

async function logInOrder(urls) {
  // 并发读取远程URL
  const textPromises = urls.map(async url => {
    const response = await fetch(url);
    return response.text();
  });

  // 按次序输出
  for (const textPromise of textPromises) {
    console.log(await textPromise);
  }
}
复制代码

上面代码中,虽然map方法的参数是async函数,但它是并发执行的,由于只有async函数内部是继发执行,外部不受影响。后面的for..of循环内部使用了await,所以实现了按顺序输出。

4.参考网址

http://es6.ruanyifeng.com/#docs/async https://github.com/xitu/gold-miner/pull/3738/files https://juejin.im/post/596e142d5188254b532ce2da https://juejin.im/post/5ade84c951882567113ad246

原文可访问个人博客

相关文章
相关标签/搜索