本期精读的文章是：[6 Reasons Why JavaScript’s Async/Await Blows Promises Away](https://hackernoon.com/6-reasons-why-javascripts-async-await-blows-promises-away-tutorial-c7ec10518dd9)

# 1 引言

<img src="https://img.alicdn.com/imgextra/i4/O1CN01HcU2pR1XbTts6EZS5_!!6000000002942-2-tps-594-288.png" alt="logo" width="500" />

我为什么要选这篇文章呢？

前端异步问题处理一直是一个老大难的问题，前有 Callback Hell 的绝望，后有 Promise/Deferred 的规范混战，从 Generator 配合 co 所向披靡，到如今 Async/Await 改变世界。为什么异步问题如此难处理，Async/Await 又能在多大程度上解决我们开发和调试过程中遇到的难点呢？希望这篇文章能给我们带来一些启发。

当然，本文不是一篇针对前端异步问题综合概要性的文章，更多的是从 Async/Await 的优越性谈起。但这并不妨碍我们从 Async/Await 的特点出发，结合自己在工作、开发过程中的经验教训，认真的思考和总结如何更优雅、更高效的处理异步问题。

# 2 内容概要

Async/Await 的优点：

 - 语法简洁清晰，节省了很多不必要的匿名函数
 - 直接使用 try...catch... 进行异常处理
 - 添加条件判断更符合直觉
 - 减少不必要的中间变量
 - 更清晰明确的错误堆栈
 - 调试时可以轻松给每个异步调用加断点

Async/Await 的局限：

 - 降低了我们阅读理解代码的速度，此前看到 `.then()` 就知道是异步，现在需要识别 `async` 和 `await` 关键字
 - 目前支持 Async/Await 的 Node.js 版本（Node 7）并非 LTS 版本，但是下一个 LTS 版本很快将会发布

可以看出，文中提到 Async/Await 的优势大部分都是从开发调试效率提升层面来讲的，提到的问题或者说局限也只有不痛不痒的两点。

让我们来看看参与精读的同学都提出了哪些深度观点：

# 3 精读

本次提出独到观点的同学有：[@javie007](http://link.zhihu.com/?target=https%3A//github.com/javie007)  [@流形](https://www.zhihu.com/people/6c772f9726a914ed4a4b90c88010461c) [@camsong](https://www.zhihu.com/people/078cc0fb15845759ad8295b0f0e50099) [@Turbe Xue](https://www.zhihu.com/people/turbe-xue) [@淡苍](https://www.zhihu.com/people/5ac53c9c0484e83672e1c1716bdf0ff9) [@留影](https://www.zhihu.com/people/38c3c75795824de1bc5d99cff904a832)  [@黄子毅](https://www.zhihu.com/people/3ec85a04bc9eaa35b1830874cc463a52) 精读由此归纳。

### Async/Await 并不是什么新鲜概念

参与精读的很多同学都提出来，Async/Await 并不是什么新鲜的概念，事实的确如此。

早在 2012 年微软的 C# 语言发布 5.0 版本时，就正式推出了 Async/Await 的概念，随后在 Python 和 Scala 中也相继出现了 Async/Await 的身影。再之后，才是我们今天讨论的主角，ES 2016 中正式提出了 Async/Await 规范。

以下是一个在 C# 中使用 Async/Await 的示例代码：

```c-sharp
public async Task<int> SumPageSizesAsync(IList<Uri> uris) 
{
    int total = 0;
    foreach (var uri in uris) {
        statusText.Text = string.Format("Found {0} bytes ...", total);
        var data = await new WebClient().DownloadDataTaskAsync(uri);
        total += data.Length;
    }
    statusText.Text = string.Format("Found {0} bytes total", total);
    return total;
}
```

再看看在 JavaScript 中的使用方法：

```javascript
async function createNewDoc() {
  let response = await db.post({}); // post a new doc
  return await db.get(response.id); // find by id
}
```

不难看出两者单纯在异步语法上，并没有太多的差异。这也是为什么 Async/Await 推出后，获得不少赞许和亲切感的原因之一吧。

其实在前端领域，也有不少类 Async/Await 的实现，其中不得不提到的就是知名网红之一的老赵写的 [wind.js](https://github.com/JeffreyZhao/wind)，站在今天的角度看，windjs 的设计和实现不可谓不超前。

### Async/Await 是如何实现的

根据 [Async/Await 的规范](https://tc39.github.io/ecmascript-asyncawait/) 中的描述 —— 一个 Async 函数总是会返回一个 Promise —— 不难看出 Async/Await 和 Promise 存在千丝万缕的联系。这也是为什么不少参与精读的同学都说，Async/Await 不过是一个语法糖。

单谈规范太枯燥，我们还是看看实际的代码。下面是一个最基础的 Async/Await 例子：

```javascript
async function test() {
  const img = await fetch('tiger.jpg');
}
```

使用 Babel 转换后：

```javascript
'use strict';

var test = function() {
    var _ref = _asyncToGenerator(regeneratorRuntime.mark(function _callee() {
        var img;
        return regeneratorRuntime.wrap(function _callee$(_context) {
            while (1) {
                switch (_context.prev = _context.next) {
                    case 0:
                        _context.next = 2;
                        return fetch('tiger.jpg');

                    case 2:
                        img = _context.sent;

                    case 3:
                    case 'end':
                        return _context.stop();
                }
            }
        }, _callee, this);
    }));

    return function test() {
        return _ref.apply(this, arguments);
    };
}();

function _asyncToGenerator(fn) {
    return function() {
        var gen = fn.apply(this, arguments);
        return new Promise(function(resolve, reject) {
            function step(key, arg) {
                try {
                    var info = gen[key](arg);
                    var value = info.value;
                } catch (error) {
                    reject(error);
                    return;
                }
                if (info.done) {
                    resolve(value);
                } else {
                    return Promise.resolve(value).then(function(value) {
                        step("next", value);
                    }, function(err) {
                        step("throw", err);
                    });
                }
            }
            return step("next");
        });
    };
}
```

不难看出，Async/Await 的实现被转换成了基于 Promise 的调用。值得注意的是，原来只需 3 行代码即可解决的问题，居然被转换成了 52 行代码，这还是基于执行环境中已经存在 regenerator 的前提之一。如果要在兼容性尚不是非常理想的 Web 环境下使用，代码 overhead 的成本不得不纳入考虑。

### Async/Await 真的是更优秀的替代方案吗

不知道是个人观察偏差，还是大家普遍都有这样的看法。在国内前端圈子里，并没有对 Async/Await 的出现表现出多么大的兴趣，几种常见的观点是：「还不是基于 Promise 的语法糖，没什么意思」、「现在使用 co 已经能完美解决异步问题，不需要再引入什么新的概念」、「浏览器兼容性这么差，用 Babel 编译又需要引入不少依赖，使用成本太高」等等。

在本次精读中，也有不少同学指出了使用 Async/Await 的局限性。

比如，使用 Async/Await 并不能很好的支持异步并发。考虑下面这种情况，一个模块需要发送 3 个请求并在获得结果后才能进行渲染，3 个请求之间没有依赖关系。如果使用 Async/Await，写法如下：

```javascript
async function mount() {
  const result1 = await fetch('a.json');
  const result2 = await fetch('b.json');
  const result3 = await fetch('c.json');

  render(result1, result2, result3);
}
```

这样的写法在异步上确实简洁不少，但是 3 个异步请求是顺序执行的，并没有充分利用到异步的优势。要想实现真正的异步，还是需要依赖 `Promise.all` 封装一层：

```javascript
async function mount() {
  const result = await Promise.all([
    fetch('a.json'),
    fetch('b.json'),
    fetch('c.json')
  ]);

  render(...result);
}
```

此外，正如在上文中提到的，async 函数默认会返回一个 Promise，这也意味着 Promise 中存在的问题 async 函数也会遇到，那就是 —— 默认会静默的吞掉异常。

所以，虽然 Async/Await 能够使用 try...catch... 这种符合同步习惯的方式进行异常捕获，你依然不得不手动给每个 await 调用添加 try...catch... 语句，否则，async 函数返回的只是一个 reject 掉的 Promise 而已。


### 异步还有哪些问题需要解决

虽然处理异步问题的技术一直在进步，但是在实际工程实践中，我们对异步操作的需求也在不断扩展加深，这也是为什么各种 flow control 的库一直兴盛不衰的原因之一。

在本次精读中，大家肯定了 Async/Await 在处理异步问题的优越性，但也提到了其在异步问题处理上的一些不足：

 - 缺少复杂的控制流程，如 always、progress、pause、resume 等
 - 缺少中断的方法，无法 abort

当然，站在 EMCA 规范的角度来看，有些需求可能比较少见，但是如果纳入规范中，也可以减少前端程序员在挑选异步流程控制库时的纠结了。


# 3 总结

Async/Await 的确是更优越的异步处理方案，但我们相信这一定不是终极处理方案。随着前端工程化的深入，一定有更多、更复杂、更精细的异步问题出现，同时也会有迎合这些问题的解决方案出现，比如精读中很多同学提到的 RxJS 和 js-csp。


> 讨论地址是：[那些年我们处理过的异步问题 · Issue #6 · dt-fe/weekly](http://link.zhihu.com/?target=https%3A//github.com/dt-fe/weekly/issues/6)

> 如果你想参与讨论，请[点击这里](https://github.com/dt-fe/weekly)，每周都有新的主题，每周五发布。
