|
| 1 | +Async Function |
| 2 | +======== |
| 3 | + |
| 4 | +Async Function,即异步函数,为异步编程带来了非常大的提升,瞬间把开发效率和维护效率提升了一个数量级。 |
| 5 | + |
| 6 | +它的用法非常简单,只要用 `async` 关键字声明一个函数为“异步函数”,这个函数的内部就可以使用 await 关键字,让其中的语句等待异步执行的结果,然后再继续执行。我们还是用代码来说话吧: |
| 7 | + |
| 8 | +```javascript |
| 9 | +function resolveAfter2Seconds(x) { |
| 10 | + return new Promise(resolve => { |
| 11 | + setTimeout(() => { |
| 12 | + resolve(x); |
| 13 | + }, 2000); |
| 14 | + }); |
| 15 | +} |
| 16 | + |
| 17 | +async function f1() { |
| 18 | + var x = await resolveAfter2Seconds(10); |
| 19 | + console.log(x); |
| 20 | +} |
| 21 | +f1(); |
| 22 | + |
| 23 | +// 输出: |
| 24 | +// (2秒后)10 |
| 25 | +``` |
| 26 | + |
| 27 | +这段代码来自 [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await)。里面先声明了 `resolveAfter2Seconds` 函数,执行它会返回一个 Promise 对象,等待2秒钟之后完成。然后声明了异步函数 `f1`,里面只有两行代码,第一行用 `await` 表示要等后面的 Promise 完成,再进行下一步;于是2秒之后,输出了“10”。 |
| 28 | + |
| 29 | +这段代码比较短,不太能体现异步函数的价值,我们改写一下查找最大文件的代码试试看: |
| 30 | + |
| 31 | +```javascript |
| 32 | +const fs = require('./FileSystem'); |
| 33 | + |
| 34 | +async function findLargest(dir) { |
| 35 | + let files = await fs.readdir(dir); |
| 36 | + let stats = []; |
| 37 | + for (let i = 0, len = files.length; i < len; i++) { |
| 38 | + stats.push(await fs.stat(files[i])); |
| 39 | + } |
| 40 | + let file = stats |
| 41 | + .filter( stat => stat.isFile() ) |
| 42 | + .reduce( (memo, stat) => { |
| 43 | + if (memo.size > stat.size) return memo; |
| 44 | + return stat; |
| 45 | + }); |
| 46 | + return file; |
| 47 | +} |
| 48 | + |
| 49 | +findLargest('path/to/dir') |
| 50 | + .then(file => { |
| 51 | + console.log(file); |
| 52 | + }); |
| 53 | +``` |
| 54 | + |
| 55 | +怎么样,是不是异常清晰明了,连代码量都减少了很多。而且,因为每次 `await` 都会等待后面的异步函数完成,所以我们还无意间写出了生成队列的代码。当然你可能不希望要队列,毕竟都异步了,队列的吸引力实在不大,那么我们只需要把它改成下面这种样子就行了: |
| 56 | + |
| 57 | +```javascript |
| 58 | +let stats = await Promise.all(files.map( file => fs.stat(file) )); |
| 59 | +``` |
| 60 | + |
| 61 | +酷不酷!想不想学? |
| 62 | + |
| 63 | +## 设计 |
| 64 | + |
| 65 | +见识过异步函数的强大之后,我们来捋一捋一下它的具体设计。 |
| 66 | + |
| 67 | +异步函数主要由两部分组成: |
| 68 | + |
| 69 | +### `async` |
| 70 | + |
| 71 | +声明一个函数是异步函数。执行异步函数会返回一个 Promise 实例。异步函数的返回值会作为 `.then()` 的参数向后传递。 |
| 72 | + |
| 73 | +`async` 的语法比较简单,就在普通函数前面加个 `async` 而已。 |
| 74 | + |
| 75 | +### `await` |
| 76 | + |
| 77 | +`await` 操作符只能用在异步函数的内部。表示等待一个 Promise 完成,然后返回其返回值。语法是这样的: |
| 78 | + |
| 79 | +```javascript |
| 80 | +[return_value] = await expression; |
| 81 | +``` |
| 82 | + |
| 83 | +**return_value 返回值**:Promise 执行器的返回值 |
| 84 | +**expression 表达式**:Promise 对象,其它类型会使用 `Promise.resolve()` 转换 |
| 85 | + |
| 86 | +使用 `await` 之后,异步函数会暂停执行,等待表达式里的 Promise 完成。`resolve` 之后,返回 Promise 执行器的返回值,然后继续执行。如果 Promise 被 `rejected`,就抛出异常。 |
| 87 | + |
| 88 | +## 捕获错误 |
| 89 | + |
| 90 | +异步函数最大的改进其实就在捕获错误这里。它可以直接使用 `try/catch/throw` 就想我们之前那样。 |
| 91 | + |
| 92 | +比如,一款前后端分离的应用,比如论坛块,需要用户登录,所以在初始化的时候就会去后端请求用户信息,如果返回了,就继续加载用户关注列表;如果 401,就让用户登录。用 Promise 的话,我们这样做: |
| 93 | + |
| 94 | +```javascript |
| 95 | +function getMyProfile() { |
| 96 | + return fetch('api/me') |
| 97 | + .then(response => { |
| 98 | + if (response.ok) { |
| 99 | + return response.json(); |
| 100 | + } |
| 101 | + }) |
| 102 | + .then(profile => { |
| 103 | + global.profile = profile |
| 104 | + }) |
| 105 | + .catch( err => { |
| 106 | + if (err.statusCode === 401) { |
| 107 | + location.href = '#/login'; |
| 108 | + } |
| 109 | + }) |
| 110 | +} |
| 111 | +``` |
| 112 | + |
| 113 | +这样做也不是不可以,但是如果业务逻辑越来越复杂,队列也会越来越长,也许每一步都需要处理错误,那么队列就会变得难以维护。 |
| 114 | + |
| 115 | +如果用异步函数就会简单很多: |
| 116 | + |
| 117 | +```javascript |
| 118 | +async function getMyProfile() { |
| 119 | + let profile; |
| 120 | + try { |
| 121 | + profile = await fetch('api/me'); |
| 122 | + global.profile = profile; |
| 123 | + } catch (e) { |
| 124 | + if (e.statusCode === 401) { |
| 125 | + location.href = '#/login'; |
| 126 | + } |
| 127 | + } |
| 128 | +} |
| 129 | +``` |
| 130 | + |
| 131 | +另外,如果你真的捕获到错误,你会发现,它的堆栈信息是连续的,甚至可以回溯到调用异步函数的语句,继而可以审查所有堆栈里的变量。这对调试程序带来的帮助非常巨大。 |
| 132 | + |
| 133 | +## 普及率 |
| 134 | + |
| 135 | +[](http://caniuse.com/#search=async%20function) |
| 136 | +截图于:2017-06-23 |
0 commit comments