JavaScript异步编程大冒险: Async/Await

Async/Await 是什么?

Async/Await 也就是你们知道的异步函数,它是一个用来控制 JavaScript 异步流程的一个记号。而在不少现代浏览器上也曾实现过这样的设想。它的灵感来源于C# 和 F#,如今 Async/Await 在ES2017已经平稳着陆。javascript

一般咱们认为 async function 是一个能返回 Promisefunction 。你也能够在 async function 使用 await 关键字。 await 关键字能够放在一个须要返回Promise的表达式前,所获得的值被从Promise里面剥离开,以便能用更直观的同步体验。咱们来看一下实际的代码更直观。java

// 这是一个简单的返回 Promise 函数
// 功能是在两秒之后 resolve("MESSAGE") .
function getMessage() {
  return new Promise((resolve, reject) => {
    setTimeout(() => resolve("MESSAGE"), 2000);
  });
}
复制代码
async function start() {
  const message = await getMessage();
  return `The message is: ${message}`;
}
复制代码
start().then(msg => console.log(msg));
// "The message is: MESSAGE"
复制代码

为何要用 Async/Await?

Async/Await 提供了一个看起来相对同步的方法来执行异步代码。同时也提供了一种简洁而直观的方法来处理异步的错误,由于它实现了try…catch 标记,这是JavaScript里面最多见的一种同步模式。node

在咱们开始冒险以前,咱们应该清楚,Async/Await 是创建在 JavaScript Promises 上的,并且关于它的知识是很重要的。json

关于记号

Async 函数

要建立一个 async 函数,通常就要把 async 关键字放在声明函数以前,就像这样:api

async function fetchWrapper() {
  return fetch('/api/url/');
}

const fetchWrapper = async () => fetch('/api/url/');
复制代码
const obj = {
  async fetchWrapper() {
    // ...
  }
}
复制代码

Await 关键字

async function updateBlogPost(postId, modifiedPost) {
  const oldPost = await getPost(postId);
  const updatedPost = { ...oldPost, ...modifiedPost };
  const savedPost = await savePost(updatedPost);
  return savedPost;
}
复制代码

在这里的 await 是用在其余返回 promise 的函数前。在第一行,oldPost被赋值为getPost执行resolve后返回的value。在下一行,咱们使用了解构赋值来演示怎样把 oldPost 和 modifiedPost 合并。最终咱们把 post 储存下来,返回了 savedPost 的结果。promise

示例 / FAQ

🖐️“到底怎么处理错误?”浏览器

这是一个好问题!当你使用 async/await 的时候,你也能够使用 try...catch 。在下面展现了,咱们异步的 fetch 了一些东西,返回了某种错误,咱们能够在catch里面拿到错误。bash

async function tryToFetch() {
  try {
    const response = await fetch('/api/data', options);
    return response.json();
  } catch(err) {
    console.log(`An error occured: ${err}`);
    // 比起返回一个错误
    // 咱们能够返回一个空的data
    return { data: [] };
  }
}
复制代码
tryToFetch().then(data => console.log(data));
复制代码

🖐️ ️“我仍是不知道为何 async/await 比 callbacks/promises 好.”并发

很高兴你问了这个问题,这里有一个例子能够说明不一样。咱们这里只是想要异步的 fetch 一些数据,而后获得数据后,简单的返回一些通过处理的data,若是有错误,咱们简单的只是想要返回一个对象。app

// 咱们这里有 fetchSomeDataCB, 和 processSomeDataCB
// NOTE: CB 表明 callback

function doWork(callback) {
  fetchSomeDataCB((err, fetchedData) => {
    if(err) {
      callback(null, [])
    }

    processSomeDataCB(fetchedData, (err2, processedData) => {
      if(err2) {
        callback(null, []);
      }

      // return the processedData outside of doWork
      callback(null, processedData);
    });
  });
}

doWork((err, processedData) => console.log(processedData));
复制代码
// 咱们这里有 fetchSomeDataP, 和 processSomeDataP
// NOTE: P 意味着这个函数返回一个 Promise

function doWorkP() {
  return fetchSomeDataP()
    .then(fetchedData => processSomeDataP(fetchedData))
    .catch(err => []);
}

doWorkP().then(processedData => console.log(processedData));
复制代码
async function doWork() {
  try {
    const fetchedData = await fetchSomeDataP();
    return processSomeDataP(fetchedData);
  } catch(err) {
    return [];
  }
}

doWork().then(processedData => console.log(processedData));
复制代码

Callback vs Promise vs Async/Await

🖐️“他的并发性如何”

当咱们须要有顺序的作一些事情,咱们一般用await一个一个声明全部的步骤。在这以前为了理解并发,咱们必须使用Promise.all。若是咱们如今有三个异步动做须要平行执行,在加上await以前,咱们须要让全部的Promise先开始。

// 这不是解决方法,他们会逐个执行
async function sequential() {
  const output1 = await task1();
  const output2 = await task2();
  const output3 = await task3();
  return combineEverything(output1, output2, output3);
}
复制代码

由于上述代码只是依次的执行了三个任务,而没有并发的执行,后一个会依赖前一个执行完成。因此咱们要改形成Promise.all 的方式。

// 这就能够并发的执行
async function parallel() {
  const promises = [
    task1(),
    task2(),
    task3(),
  ];
  const [output1, output2, output 3] = await Promise.all(promises);
);
  return combineEverything(output1, output2, output3);
}
复制代码

在这个例子上,咱们首先执行了3个异步的任务,以后把Promise都储存进了一个array。咱们使用 Promise.all 来完成来所有并发结果的收集。

另外的一些提示

  • 你很容易会忘记每次你 await 一些代码, 你须要声明这个函数是一个 async function
  • 当你使用 await 时候,它值暂停了所涉及的 async function 。 换句话说,下面的代码会在其余东西log以前log 'wanna race?'
const timeoutP = async (s) => new Promise((resolve, reject) => {
  setTimeout(() => resolve(s*1000), s*1000)
});
复制代码
[1, 2, 3].forEach(async function(time) {
  const ms = await timeoutP(time);
  console.log(`This took ${ms} milliseconds`);
});
复制代码
console.log('wanna race?');
复制代码

当你的第一个await 的 promise在主线程上返回执行告终果,在forEach外面的log不会被阻塞。

浏览器支持

看一看这张浏览器支持表

Node 支持

node 7.6.0 以及以上版本支持 Async/Await !

做者:Benjamin Diuguid

原文:Asynchronous Adventures in JavaScript: Async/Await

翻译:Dominic Ming

相关文章
相关标签/搜索