【译】async/await 应知应会

译文出自:闪电矿工翻译组javascript

原文地址: JavaScript async/awaitjava

原文做者: Charlee Ligit

仓库原文连接:issuegithub

译者: Xixi20160512typescript

async/await 是在 ES7 版本中引入的,它对于 JavaScript 中的异步编程而言是一个巨大的提高。它可让咱们以同步的方式处理异步的流程,同时不会阻塞主线程。可是,想要用好这一特性,可能须要动点脑筋。本文中,咱们将从不一样的角度探讨 async/await,同时会展现如何正确和高效的使用它们。编程

async/await 的优势

async/await带给咱们最大的一个好处就是同步的编程风格。让咱们看一个例子:promise

// async/await
async getBooksByAuthorWithAwait(authorId) {
    const books = await bookModel.fetchAll();  
    return books.filter(b => b.authorId === authorId);
}
// promise
getBooksByAuthorWithPromise(authorId) { 
    return bookModel.fetchAll()
        .then(books => books.filter(b => b.authorId === authorId));
}
复制代码

很明显,async/await 的版本比 promise 的版本更加的易于理解。若是你忽略 await 关键字,这段代码看起来就像任何其余的同步式语言(好比说 Python)。浏览器

不只仅是可读性,async/await 有浏览器的原生支持。到今天为止,全部主流浏览器都支持 async 函数。安全

68747470733a2f2f63646e2d696d616765732d312e6d656469756d2e636f6d2f6d61782f3830302f312a633662596168414255447047674d704a56616b3441672e706e67

全部主流浏览器都支持 async 函数。(图片来源:caniuse.com/)app

原生支持意味着你不须要编译代码。更重要的是,这个将有助于调试。当你在 async 方法的入口打一个断点而且步进到 await 这一行的时候,你将会看到调试器在 bookModel.fetchAll() 这个函数执行的时候等待了一下子,而后才会走到接下来的 .filter 这一行!和 promise 的示例比较起来,这个容易多了,由于你必须在 .filter 这一行再打一个断点。

2

调试 async 函数。调试器会在 await 这一行等待执行完成而后才会移动到下一行。

另外一个不那么明显的好处就是 async 关键字。它声明了 getBooksByAuthorWithAwait() 方法返回的是一个 promise,所以调用者能够像 getBooksByAuthorWithAwait().then(...) 或者 await getBooksByAuthorWithAwait() 这样安全的调用。看一下这个例子(很差的实践):

getBooksByAuthorWithPromise(authorId) {  
    if (!authorId) {    return null;  }  
    return bookModel.fetchAll()    
        .then(books => books.filter(b => b.authorId === authorId));
}
复制代码

在上面的代码中,getBooksByAuthorWithPromise 可能返回一个 promise (正常状况下)或者 null (特殊状况下),返回 null 的时候调用者不能安全的调用 .then() 。使用 async 进行声明的时候,这个问题就不会存在了。

Async/await 可能会产生误导

一些文章把 async/await 和 Promise 进行了比较,同时说它是 JavaScript 异步编程演变过程当中的下一代解决方案,对此我不敢苟同。Async/await 是一个提高,但它仅仅是一个语法糖,它将不会彻底的改变咱们的编程风格。

实质上,async 函数仍然是 promise。你必须理解 promises 以后才能正确的使用 async 函数,更糟糕的是,大多数状况下你必须同时使用 promises 和 async 函数。

思考一下上面例子中使用到 的 getBooksByAuthorWithAwait()getBooksByAuthorWithPromises() 。请注意,它们不只是有相同的功能,同时也有相同的接口。

这意味着若是你直接 getBooksByAuthorWithAwait() 的话,将会返回一个 promise。

固然,这并非一件很差的事情。只有 await 给人们的一种感受,“很棒,这个能够将异步的函数转换成同步的函数”,这个才是错误的。

Async/await 的陷阱

那么在使用 async/await 的过程当中会犯哪些错误呢?这里有一些比较常见的例子。

过于线性化

虽然 await 可以使你的代码看起来像同步代码同样,可是必定要记住这些代码仍然是以异步的方式执行的,注意不要使代码过于线性化。

async getBooksAndAuthor(authorId) {  
    const books = await bookModel.fetchAll();  
    const author = await authorModel.fetch(authorId);  
    return {    
        author,    
        books: books.filter(book => book.authorId === authorId),  
    };
}
复制代码

这段代码看起来逻辑上没有问题。然而是不正确的。

  1. await bookModel.fetchAll() 将会等待 fetchAll() 执行完。
  2. 而后 await authorModel.fetch(authorId) 才会被执行

注意, authorModel.fetch(authorId) 并不依赖 bookModel.fetchAll() 的结果,实际上他们能够并行执行。然而,因为使用了 await 这两次调用就变成了串行的了,花费的总时间将会远超并行的方式。

如下是正确的使用方式:

async getBooksAndAuthor(authorId) {  
    const bookPromise = bookModel.fetchAll();  
    const authorPromise = authorModel.fetch(authorId);  
    const book = await bookPromise;  
    const author = await authorPromise;  
    return {    
        author,    
        books: books.filter(book => book.authorId === authorId),  
    };
}
复制代码

或者更复杂的状况下,若是你想依次请求一个列表的内容,你必须依赖 promises:

async getAuthors(authorIds) {  
    // WRONG, this will cause sequential calls 
    // const authors = _.map( 
    // authorIds, 
    // id => await authorModel.fetch(id));
// CORRECT 
    const promises = _.map(authorIds, id => authorModel.fetch(id));  
    const authors = await Promise.all(promises);
}
复制代码

简而言之,你必须把这个工做流程当作是异步的,而后再尝试使用 await 以同步的方式去编写代码。在复杂的流程下面,直接使用 promises 可能会更简单。

错误处理

使用 promises 的状况下,一个异步函数会返回两种可能的值:resolved 和 rejected。咱们可使用 .then() 来处理正常的状况 .catch() 处理异常状况。然而对于 async/await 来讲,异常处理可能会有点诡异。

try...catch

最标准的(也是我推荐的)处理方式是使用 try...catch 表达式。当 await 一个函数调用的时候,任何 rejected 的值都会以异常的形式抛出来。这里有个例子:

class BookModel {  
    fetchAll() {    
        return new Promise((resolve, reject) => {      
            window.setTimeout(() => { 
                reject({'error': 400}) 
            }, 1000);    
        });  
    }
}
// async/await
async getBooksByAuthorWithAwait(authorId) {
    try {  
        const books = await bookModel.fetchAll();
    } catch (error) {  
        console.log(error);    // { "error": 400 }
    }
}
复制代码

被捕获的错误就是 rejected 的值。在咱们捕获这个异常以后,咱们有不少方式来处理它:

  • 处理掉这个异常,而后返回一个正常的值。(没有在 catch 块中使用任何 return 表达式等价于使用 return undefined ;同时,返回的还是一个 resolved 的值。)
  • 抛出这个异常,若是你但愿调用者去处理它。你能够直接抛出原始的错误对象,例如 throw error; ,这种方式容许你以 promise 链式的方式使用 async getBooksByAuthorWithAwait() 方法(列如,你仍然能够像 getBooksByAuthorWithAwait().then(...).catch(error => ...) 这样调用它);或者,你可使用 Error 对象包装错误对象,例如, throw new Error(error) ,使用这种方式能够在控制台中展现全部的调用栈记录。
  • 使用 Reject,例如, return Promise.reject(error) ,这个方式等价于 throw error ,所以不推荐使用这种方式。

使用 try...catch 的优势有如下这些:

  • 简单,传统。只要你有其余语言的经验,例如 C++ 或 Java,理解这种处理方式将不会有任何困难。
  • 你能够将多个 await 调用包装在一个 try...catch 块中来集中处理全部错误,若是每一步的错误处理非必要的话。

这种处理方式有一个缺陷。因为 try...catch 将会捕获这个代码块中的全部异常,一些其余一般不会被 promises 捕获的异常也会被捕获住。考虑一下这个例子:

class BookModel {  
    fetchAll() {    
        cb();    // note `cb` is undefined and will result an exception 
        return fetch('/books');  
    }
}
try {  
    bookModel.fetchAll();
} catch(error) {  
    console.log(error);  // This will print "cb is not defined"
}
复制代码

执行这段代码你将会在控制台中获得一个错误: ReferenceError: cb is not defined ,这些文字是黑色的。这个错误是 console.log() 打印出来的而不是 JavaScript 自身。某些时候这将会是致命的:若是 BookModel 被一系列函数调用深深地封闭起来了,同时,其中某一个调用将这个错误处理掉了,这时候就很难像这样去发现这个错误了。

使函数同时返回两个值

另一个错误处理的方式是由 Go 语言启发的。它容许 async 函数同时返回错误的值和正常的值。能够从下面这个博客中了解到更详细的的介绍:

How to write async await without try-catch blocks in Javascript *ES7 Async/await allows us as developers to write asynchronous JS code that look synchronous. In current JS version we…*blog.grossman.io

简而言之,你可以像下面这样使用 async 函数:

[err, user] = await to(UserModel.findById(1));
复制代码

我我的并不喜欢这种处理方式,由于它把 Go 语言的编程风格带到了 JavaScript 中,这样显得不天然,可是在某些状况下这种方式会颇有用。

使用 .catch

我要介绍的最后一种处理方式是仍然使用 .catch()

回忆一下 await 的功能:它会等待一个 promise 完成它的任务。同时请回忆一下, promise.catch() 也会返回一个 promise!所以咱们能够像下面这样处理错误处理的方式:

// books === undefined if error happens,
// since nothing returned in the catch statement
let books = await bookModel.fetchAll()  
	.catch((error) => { 
        console.log(error); 
    });
复制代码

这种处理方式有两个次要的问题:

  • 这种方式混合了 promises 和 async 函数。你仍然须要理解 promises 的运行原理以后才能读懂它。
  • 错误处理在正常流程以前,这样是不太直观的。

结论

在 ES7 中引入的 async/await 关键字无疑是对 JavaScript 异步编程的一大增强。它可以把代码变得更易于阅读和调试。而后,为了正确的使用它们,必需要彻底理解 promises,由于它们不过是语法糖,底层的技术仍然是 promises。

但愿这篇文章可以给你一些关于 async/await 的启发,同时可以帮助你避免一些常见的错误。感谢阅读,若是喜欢的话,请为我点赞。

相关文章
相关标签/搜索