Async/Await胜过Promise的6个理由(自译)

原文链接

In case you missed it, Node now supports async/await out of the box since version 7.6. If you haven’t tried it yet, here are a bunch of reasons with examples why you should adopt it immediately and never look back.

如果你错过了它,Node7.6现在已经支持async/await。如果你至今还没有试过,本文将会有许多理由和例子来告诉你为什么你应该马上使用async/await并且再也不回换回之前的写法。

Async/await 101
For those who have never heard of this topic before, here’s a quick intro

  • Async/await is a new way to write asynchronous code. Previous options for asynchronous code are callbacks and promises.
  • Async/await is actually built on top of promises. It cannot be used with plain callbacks or node callbacks.
  • Async/await is, like promises, non blocking.
  • Async/await makes asynchronous code look and behave a little more like synchronous code. This is where all its power lies.

Async/await基础

这里给没有从没用过async/await的读者进行一个简单的介绍

  • async/await是一种书写异步代码的新方法,在这之前使用回调函数和promise来设置异步代码。
  • async/await实际上是在promise的基础上构建的,他不能和纯回调函数(plain callbacks)和节点回调函数(node callbacks)一起使用。
  • async/await很像promise,是非阻塞的。
  • async/await让异步代码的外观和行为像同步代码。这就是他厉害的地方。

Syntax
Assuming a function getJSON that returns a promise, and that promise resolves with some JSON object. We just want to call it and log that JSON, then return “done”.
This is how you would implement it using promises

语法

假设一个函数getJSON返回一个promise,并且该promise会去reslove一些JSON对象,我们只是像简单的调用它并且打印该JSON,之后再返回"done"

你可能用promise这样去实现

1
2
3
4
5
6
7
const makeRequest= () =>
getJSON()
.then(data => {
console.log(data)
return "done"
})
makeRequest()

And this is how it looks with async/await

用async/await会长这样

1
2
3
4
5
6
const makeRequest = async () => {
console.log(await getJSON())
return "done"
}
makeRequest()

There are a few differences here

  1. Our function has the keyword async before it. The await keyword can only be used inside functions defined with async. Any async function returns a promise implicitly, and the resolve value of the promise will be whatever you return from the function (which is the string “done” in our case).
  2. The above point implies that we can’t use await in the top level of our code since that is not inside an async function.
  3. await getJSON() means that the console.log call will wait until getJSON() promise resolves and print it value.

有几点不同

  1. 我们的函数前面有一个关键字asyncawait关键字只能在async定义的函数内部使用,任何async函数返回一个隐式的promise,并且这个promise的resolve将会是这个函数中的return(在上例中是字符串"node")。
  2. 前面一点暗示了我们不能在代码的最外层使用await,因为那不再一个async函数内部。

    1
    2
    3
    4
    5
    6
    7
    // this will not work in top level
    // await makeRequest()
    // this will work
    makeRequest().then((result) => {
    // do something
    })
  3. await getJSON()意味着console.log()的调用会等到getJSON()resolve之后。

Why Is It better?
1.Concise and clean
Look at how much code we didn’t write! Even in the contrived example above, it’s clear we saved a decent amount of code. We didn’t have to write .then, create an anonymous function to handle the response, or give a name data to a variable that we don’t need to use. We also avoided nesting our code. These small advantages add up quickly, which will become more obvious in the following code examples.

为什么async/await更好?

1. 简洁干净

看一看我们少写了多少代码!甚至在上面故意这样写的例子里,显然我们节省了大量代码。我们不用写.then,不用创建一个匿名函数来处理response,或者是给data赋值给一个我们不需要的变量,我们也避免了嵌套代码。这些小的优点汇聚起来,将在接下来的例子中更加明显。

2.Error handling
Async/await makes it finally possible to handle both synchronous and asynchronous errors with the same construct, good old try/catch. In the example below with promises, the try/catch will not handle if JSON.parse fails because it’s happening inside a promise. We need to call .catch on the promise and duplicate our error handling code, which will (hopefully) be more sophisticated than console.log in your production ready code.

2. 异常处理

async/await 终于让使用同样的结构,好并且成熟的try/catch来处理同步和异步异常成为可能。在下面使用promise的例子中,当JSON.parse失败时,try/catch不会去处理异常,因为错误在promise内部产生,我们需要在promise上调用.catch并重复你异常处理的代码。这用法看起来会比在你的成品代码里使用console.log更加成熟。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const makeRequest = () => {
try {
getJSON()
.then(result => {
// this parse may fail
const data = JSON.parse(result)
console.log(data)
})
// uncomment this block to handle asynchronous errors
// .catch((err) => {
// console.log(err)
// })
} catch (err) {
console.log(err)
}
}

Now look at the same code with async/await. The catch block now will handle parsing errors.

现在来看一下相同的代码使用async/await,catch代码块将会处理解析异常。

1
2
3
4
5
6
7
8
9
const makeRequest = async () => {
try {
// this parse may fail
const data = JSON.parse(await getJSON())
console.log(data)
} catch (err) {
console.log(err)
}
}

3.Conditionals
Imagine something like the code below which fetches some data and decides whether it should return that or get more details based on some value in the data.

3. 条件语句

想象一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const makeRequest = () => {
return getJSON()
.then(data => {
if (data.needsAnotherRequest) {
return makeAnotherRequest(data)
.then(moreData => {
console.log(moreData)
return moreData
})
} else {
console.log(data)
return data
}
})
}

Just looking at this gives you a headache. It’s easy to get lost in all that nesting (6 levels), braces, and return statements that are only needed to propagate the final result up to the main promise.
This example becomes way more readable when rewritten with async/await.

(上面的代码)只是看着就会头痛。很容易让人迷失在嵌套(6层),支架(?)和返回由主要promise来产生最终结果所需要的声明之中。
这个例子实现了一种用async/await重写的更可读的方法

1
2
3
4
5
6
7
8
9
10
11
const makeRequest = async () => {
const data = await getJSON()
if (data.needsAnotherRequest) {
const moreData = await makeAnotherRequest(data);
console.log(moreData)
return moreData
} else {
console.log(data)
return data
}
}

4.Intermediate values
You have probably found yourself in a situation where you call a promise1 and then use what it returns to call promise2, then use the results of both promises to call a promise3. Your code most likely looked like this

4. 中间值

你可能发现你经常遇到这样的情况:你调用一个promise1然后用它的返回值去调用promise2然后用它的返回值去调用promise3.你的代码看起来会是这样

1
2
3
4
5
6
7
8
9
10
11
const makeRequest = () => {
return promise1()
.then(value1 => {
// do something
return promise2(value1)
.then(value2 => {
// do something
return promise3(value1, value2)
})
})
}

If promise3 didn’t require value1 it would be easy to flatten the promise nesting a bit. If you are the kind of person who couldn’t live with this, you could wrap both values 1 & 2 in a Promise.all and avoid deeper nesting, like this

如果promise3不依赖value1,那么退出promise会简单一些。如果你是那种不能忍受这样写的人,你可以把value1和2都包在一个Promise.all里,同时也避免了更深的嵌套,就像这样

1
2
3
4
5
6
7
8
9
10
11
const makeRequest = () => {
return promise1()
.then(value1 => {
// do something
return Promise.all([value1, promise2(value1)])
})
.then(([value1, value2]) => {
// do something
return promise3(value1, value2)
})
}

This approach sacrifices semantics for the sake of readability. There is no reason for value1 & value2 to belong in an array together, except to avoid nesting promises.
This same logic becomes ridiculously simple and intuitive with async/await. It makes you wonder about all the things you could have done in the time that you spent struggling to make promises look less hideous.

这个方法牺牲了语法来获得可读性,没有理由让value1value2都属于同一个数组,除非是为了避免嵌套promise。
相同的逻辑用async/await将变得非常简单并且浅显易懂。这会让你怀疑你能用原本花费在绞尽脑汁让promise看起来不那么丑的时间来完成所有事情。

1
2
3
4
5
const makeRequest = async () => {
const value1 = await promise1()
const value2 = await promise2(value1)
return promise3(value1, value2)
}

5.Error stacks
Imagine a piece of code that calls multiple promises in a chain, and somewhere down the chain an error is thrown.

5. 异常栈

想象一片链式调用了多个promise的代码,在调用的某处抛出了异常。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const makeRequest = () => {
return callAPromise()
.then(() => callAPromise())
.then(() => callAPromise())
.then(() => callAPromise())
.then(() => callAPromise())
.then(() => {
throw new Error("oops");
})
}
makeRequest()
.catch(err => {
console.log(err);
// output
// Error: oops at callAPromise.then.then.then.then.then (index.js:8:13)
})

The error stack returned from a promise chain gives no clue of where the error happened. Even worse, it’s misleading; the only function name it contains is callAPromise which is totally innocent of this error (the file and line number are still useful though).
However, the error stack from async/await points to the function that contains the error

异常栈由promise链返回,不知道到底是哪里发生了异常。甚至会出更坏的情况:误导。只包含唯一的函数名callAPromise,它在这个异常中完全错误(虽然文件和行号依然能用)。
然而,由async/await得到的异常栈会指向包含错误的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const makeRequest = async () => {
await callAPromise()
await callAPromise()
await callAPromise()
await callAPromise()
await callAPromise()
throw new Error("oops");
}
makeRequest()
.catch(err => {
console.log(err);
// output
// Error: oops at makeRequest (index.js:7:9)
})

This is not a huge plus when you’re developing on your local environment and have the file open in an editor, but it’s quite useful when you’re trying to make sense of error logs coming from your production server. In such cases, knowing the error happened in makeRequest is better than knowing that the error came from a then after a then after a then

当你在本地环境,用编辑器开着文件开发,对你而言不是一个很大的好事,但是当你想从生产服务器搞清楚错误输出的话,这将会非常有用。这种情况下,知道异常出现在makeRequest里比知道异常从一堆then里好了不少。

6.Debugging
Last but not least, a killer advantage when using async/await is that it’s much easier to debug. Debugging promises has always been such a pain for 2 reasons
1.You can’t set breakpoints in arrow functions that return expressions (no body).

6. Debugging

最后但同样重要的一点,async/await的杀手锏是它debug起来更加简单,promise进行debug有两个导致他异常痛苦的原因:

  1. 你不能在一列返回表达式的函数上打断点。

    1. If you set a breakpoint inside a .then block and use debug shortcuts like step-over, the debugger will not move to the the following .then because it only “steps” through synchronous code.
      With async/await you don’t need arrow functions as much, and you can step through await calls exactly as if they were normal synchronous calls.
  2. 如果你在.then代码块内部打断点,并且用了bebug的快捷方式,比如step-over,debugger不会执行下一个.then因为他只能在同步代码中执行。
    用async/await的话你不需要一列函数,并且你可以单步调试await来像普通同步代码一样调用。

In Conclusion
Async/await is one of the most revolutionary features that have been added to JavaScript in the past few years. It makes you realize what a syntactical mess promises are, and provides an intuitive replacement.

总结

async/await是近几年javascript新增的最具革命性的特性之一。它能让你实现与杂乱的promise等价的功能,同时提供了直接了当的替换方案。

Concerns
Some valid skepticism you might have about using this feature
It makes asynchronous code less obvious: Our eyes learned to spot asynchronous code whenever we see a callback or a .then, it will take a few weeks for your eyes to adjust to the new signs, but C# had this feature for years and people who are familiar with it know it’s worth this minor, temporary inconvenience.
Node 7 is not an LTS release: Yes, but node 8 is coming next month, and migrating you codebase to the new version will most likely take little to no effort.

关注点

你需要了解关于这个特性的一些有意义的质疑

  • 它会让异步代码更加不显眼:我们的眼睛习惯了当看到回调或者.then进而关注异步代码,这将会花费你几周来让你的眼睛适应新写法,但是C#许多年前就有这个特性,并且熟悉这种写法的人知道这种写法带来的好处远远值得去克服这些很小的麻烦。
  • node7还不是LTS版本:是的,但是node8下个月(原文发布于2017年3月26日)就要出了。迁移你的代码库到新版本将很可能不需要花费太大力气。

Powered by Hexo and Hexo-theme-hiker

Copyright © 2013 - 2018 香香鸡的小窝 All Rights Reserved.

SunskyXH hold copyright