V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐关注
Meteor
JSLint - a JavaScript code quality tool
jsFiddle
D3.js
WebStorm
推荐书目
JavaScript 权威指南第 5 版
Closure: The Definitive Guide
darluc
V2EX  ›  JavaScript

[译文] Javascript 异步探奇: Aysn/Await

  •  
  •   darluc · 2017-12-12 16:38:08 +08:00 · 2590 次点击
    这是一个创建于 2541 天前的主题,其中的信息可能已经有所发展或是发生改变。

    点击查看完整版

    Async/Await 为何物?

    Async/Await 即是异步函数( Async Functions ),是 JavaScript 中控制异步流程的特殊语法。目前,大多数主流浏览器已经支持了此语法。它的诞生灵感来源于 C# 和 F# 编程语言。目前 Aysnc/Await 已进入 JavaScript/EcmaScript 2017 标准。

    简单来说,一个异步函数 async function 就是一个返回值为 Promise 对象的函数 function。在 async function 异步函数中才可以使用 await 关键字。Await 关键字应放在返回值为 Promise 对象的表达式之前,然后它会从这个 Promise 中取得解决值,虽然看上去像是一条同步执行的语句,但实际 Promise 的执行过程仍然是异步的。举个例子往往能解释得更清楚😁。

    // 这是一个普通的函数,它会返回一个 promise。
    // 该 promise 于 2 秒后解决为 "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 的语法结构,这与一般的同步代码处理错误的方式一致。

    在进一步深入之前,我们必须强调一个前提:Async/Await 是绝对依赖于 JavaScript Promises 的,想要完全理解 Async/Await 就必须先了解它。

    语法

    Async 函数

    创建一个异步函数 async function ,只需要在函数申明前加上 async 关键字即可,如下:

    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 的函数(现在也可称之为异步函数 aync functions)前。在函数的第一行 oldPost 被赋予了异步函数 getPost 返回的解决值( resolved value )。接下来,我们使用了对象展开操作符oldPostmodifiedPost 进行了一次浅层合并( shallow merge )。最后,我们保存修改过的文章,再一次使用 await 关键词接收 savePost 函数异步执行返回的 promise 对象。

    例子 /常见问题

    ✋ “怎样进行错误处理的?”

    好问题!在 async/await 的帮助下,我们可以使用与同步代码一样的语法,try … catch。下面的代码中,如果我们的异步调用 fetch 返回了某种错误,比如 404,它将会被 catch 代码捕获,之后我们就可以对这个错误进行处理了。

    async function tryToFetch() {
      try {
        const response = await fetch('/api/data', options);
        return response.json();
      } catch(err) {
        console.log(`An error occured: ${err}`);
        // Instead of rethrowing the error
        // Let's return a regular object with no data
        return { data: [] };
      }
    }
    
    tryToFetch().then(data => console.log(data));
    

    ✋ “我还是不明白为什么 async/await 语法比 callbacks/promises 语法好。”

    问得好!下面有个代码例子可展现出它们之间的区别。假设我们要异步地 fetchSomeData 获取某些数据,得到数据后再异步地 processSomeData 处理这些数据,如果有错误出现,只简单地返回一个对象。

    // we have fetchSomeDataCB, and processSomeDataCB
    // NOTE: CB stands for 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));
    
    // we have fetchSomeDataP, and processSomeDataP
    // NOTE: P means that this function returns a Promise
    
    function doWorkP() {
      return fetchSomeDataP()
        .then(fetchedData => processSomeDataP(fetchedData))
        .catch(err => []);
    }
    
    doWorkP().then(processedData => console.log(processedData));
    
    // we have fetchSomeDataP, and processSomeDataP
    // NOTE: P means that this function returns a Promise
    
    async function doWork() {
      try {
        const fetchedData = await fetchSomeDataP();
        return processSomeDataP(fetchedData);
      } catch(err) {
        return [];
      }
    }
    
    doWork().then(processedData => console.log(processedData));
    
    <center><small>Callback vs Promise vs Async/Await</small></center>

    “如何处理并发”

    如果我们想要异步过程被顺序地处理,只需要简单地使用多行 await 语句,并可将一个异步调用的输出结果传递给另一个异步调用,就像平时使用 promises 那样。不过为了理解并发,我们就必须使用 Promise.all 方法。如果想让 3 个异步动作同时执行(并发地),我们就要在 await 这些 promises 之前,让它们全部开始执行。

    // Not ideal: This will happen sequentially
    async function sequential() {
      const output1 = await task1();
      const output2 = await task2();
      const output3 = await task3();
      return combineEverything(output1, output2, output3);
    }
    

    上面的代码并不好使,因为 task2 会一直等到 task1 完成之后才开始执行,而 task3 则一直要等到 task1 和 task2 都完成之后才开始,其实这些任务之间并没有依赖关系。理想的做法是,我们让三个任务同时开始执行,进入 Promise.all 中等待它们完成。

    // Ideal: This will happen concurrently
    async function parallel() {
      const promises = [
        task1(),
        task2(),
        task3(),
      ];
      const [output1, output2, output3] = await Promise.all(promises);
    
      return combineEverything(output1, output2, output3);
    }
    

    在这段代码中,我们同时启动了三个异步任务,并将它们返回的 promises 存入一个数组。然后,将这组已执行的 promises 传入 Promise.all(),并 await 等待其返回输出。

    其它说明

    • 经常容易忘记的一点:每当你使用 await 关键字的时候,你必须在 async function 异步函数中使用。
    • 当你使用 await 时,它只会在 aync function 异步函数内产生暂停效果。如下面的例子中,会在其它的日志输出前打印出 '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?');
    

    当这些 promises 进入等待状态 awaited-ed 之后,执行流程会回到主线程, forEach 语句外面的 console.log 就会被执行。

    浏览器支持

    目前浏览器的支持情况,你可以查看这个浏览器兼容表

    Node 支持

    node 7.6.0 向上都支持 Aysnc/Await 语法!

    点击查看完整版

    6 条回复    2017-12-25 02:13:32 +08:00
    dablwow
        1
    dablwow  
       2017-12-12 17:23:06 +08:00   ❤️ 1
    呃标题没写对...async
    duan602728596
        2
    duan602728596  
       2017-12-12 22:20:44 +08:00 via iPhone   ❤️ 1
    aysn......
    darluc
        3
    darluc  
    OP
       2017-12-13 14:53:11 +08:00
    @dablwow @duan602728596 谢谢,找不到编辑按钮了
    ThomasChan
        4
    ThomasChan  
       2017-12-23 15:20:33 +08:00
    哈哈 medium 这些作者是不是还互相通知这周要写关于啥的文章啊 我昨晚也刚翻了一篇

    http://chenjunhao.cn/2017/12/22/6-%E4%B8%AA%E5%8E%9F%E5%9B%A0%E5%91%8A%E8%AF%89%E4%BD%A0-Async-Await-%E6%AF%94-Promise-%E5%A5%BD%E7%94%A8%EF%BC%88%E6%95%99%E7%A8%8B%EF%BC%89/

    原文是在 Frontend Focus 周刊里看到的,也是发在 medium 上的
    ThomasChan
        5
    ThomasChan  
       2017-12-23 15:22:25 +08:00
    好吧记错了是 Hacker Noon 上不是 medium
    darluc
        6
    darluc  
    OP
       2017-12-25 02:13:32 +08:00
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3628 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 27ms · UTC 05:00 · PVG 13:00 · LAX 21:00 · JFK 00:00
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.