async await

随着 Node 7 的发布,越来越多的人开始研究据说是异步编程终级解决方案的 async/await。我第一次看到这组关键字并不是在 JavaScript 语言里,而是在 c# 5.0 的语法中。C# 的 async/await 需要在 .NET Framework 4.5 以上的版本中使用,因此我还很悲伤了一阵——为了要兼容 XP 系统,我们开发的软件不能使用高于 4.0 版本的 .NET Framework。

我之前在《闲谈异步调用“扁平”化》 中就谈到了这个问题。无论是在 C# 还是 JavaScript 中,async/await 都是非常棒的特性,它们也都是非常甜的语法糖。C# 的 async/await 实现离不开 Task 或 Task 类,而 JavaScript 的 async/await 实现,也离不开 Promise。

现在抛开 C# 和 .NET Framework,专心研究下 JavaScript 的 async/await。

async 和 await 在干什么

任意一个名称都是有意义的,先从字面意思来理解。async 是“异步”的简写,而 await 可以认为是 async wait 的简写。所以应该很好理解 async 用于申明一个 function 是异步的,而 await 用于等待一个异步方法执行完成。

另外还有一个很有意思的语法规定,await 只能出现在 async 函数中。然后细心的朋友会产生一个疑问,如果 await 只能出现在 async 函数中,那这个 async 函数应该怎么调用?

如果需要通过 await 来调用一个 async 函数,那这个调用的外面必须得再包一个 async 函数,然后……进入死循环,永无出头之日……

如果 async 函数不需要 await 来调用,那 async 到底起个啥作用?

async 起什么作用

这个问题的关键在于,async 函数是怎么处理它的返回值的!

我们当然希望它能直接通过 return 语句返回我们想要的值,但是如果真是这样,似乎就没 await 什么事了。所以,写段代码来试试,看它到底会返回什么:

async function testAsync() {
    return "hello async";
}

const result = testAsync();
console.log(result);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

看到输出就恍然大悟了——输出的是一个 Promise 对象。

c:\var\test> node --harmony_async_await .
Promise { 'hello async' }
  • 1
  • 2

所以,async 函数返回的是一个 Promise 对象。从文档中也可以得到这个信息。async 函数(包含函数语句、函数表达式、Lambda表达式)会返回一个 Promise 对象,如果在函数中 return 一个直接量,async 会把这个直接量通过 Promise.resolve() 封装成 Promise 对象。

async 函数返回的是一个 Promise 对象,所以在最外层不能用 await 获取其返回值的情况下,我们当然应该用原来的方式:then() 链来处理这个 Promise 对象,就像这样

testAsync().then(v => {
    console.log(v);    // 输出 hello async
});
  • 1
  • 2
  • 3

现在回过头来想下,如果 async 函数没有返回值,又该如何?很容易想到,它会返回 Promise.resolve(undefined)。

联想一下 Promise 的特点——无等待,所以在没有 await 的情况下执行 async 函数,它会立即执行,返回一个 Promise 对象,并且,绝不会阻塞后面的语句。这和普通返回 Promise 对象的函数并无二致。

那么下一个关键点就在于 await 关键字了。

await 到底在等啥

一般来说,都认为 await 是在等待一个 async 函数完成。不过按语法说明,await 等待的是一个表达式,这个表达式的计算结果是 Promise 对象或者其它值(换句话说,就是没有特殊限定)。

因为 async 函数返回一个 Promise 对象,所以 await 可以用于等待一个 async 函数的返回值——这也可以说是 await 在等 async 函数,但要清楚,它等的实际是一个返回值。注意到 await 不仅仅用于等 Promise 对象,它可以等任意表达式的结果,所以,await 后面实际是可以接普通函数调用或者直接量的。所以下面这个示例完全可以正确运行

function getSomething() {
    return "something";
}

async function testAsync() {
    return Promise.resolve("hello async");
}

async function test() {
    const v1 = await getSomething();
    const v2 = await testAsync();
    console.log(v1, v2);
}

test();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

await 等到了要等的,然后呢

await 等到了它要等的东西,一个 Promise 对象,或者其它值,然后呢?我不得不先说,await 是个运算符,用于组成表达式,await 表达式的运算结果取决于它等的东西。

如果它等到的不是一个 Promise 对象,那 await 表达式的运算结果就是它等到的东西。

如果它等到的是一个 Promise 对象,await 就忙起来了,它会阻塞后面的代码,等着 Promise 对象 resolve,然后得到 resolve 的值,作为 await 表达式的运算结果。

看到上面的阻塞一词,心慌了吧……放心,这就是 await 必须用在 async 函数中的原因。async 函数调用不会造成阻塞,它内部所有的阻塞都被封装在一个 Promise 对象中异步执行。

我们来看一个关于阻塞的例子:

var a = 1 + 1;

function takeLongTime() {
    return new Promise(resolve => {
        setTimeout(() => {
            resolve('Hello World');
        }, 2000);
    });
}

async function test() {
    const v = await takeLongTime();
    console.log(a);
    console.log(v);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

这里需要注意的是, await函数其实是会阻塞后面的操作的, 它会等到await后面跟着的那个函数, 返回的promise对象执行了resolve函数之后, 才会允许代码继续往后执行, 因此这里的console.log(a)还有console.log(v)都会在takeLongTime函数返回promise对象, 延时2s后执行resolve方法之后, 才会执行两个console语句, 如果setTimeout延时100s, 这里就会等待100s

async/await 帮我们干了啥

作个简单的比较 
上面已经说明了 async 会将其后的函数(函数表达式或 Lambda)的返回值封装成一个 Promise 对象,而 await 会等待这个 Promise 完成,并将其 resolve 的结果返回出来。

现在举例,用 setTimeout 模拟耗时的异步操作,先来看看不用 async/await 会怎么写

function takeLongTime() {
    return new Promise(resolve => {
        setTimeout(() => resolve("long_time_value"), 1000);
    });
}

takeLongTime().then(v => {
    console.log("got", v);
});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

如果改用 async/await 呢,会是这样

function takeLongTime() {
    return new Promise(resolve => {
        setTimeout(() => resolve("long_time_value"), 1000);
    });
}

async function test() {
    const v = await takeLongTime();
    console.log(v);
}

test();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

眼尖的同学已经发现 takeLongTime() 没有申明为 async。实际上,takeLongTime() 本身就是返回的 Promise 对象,加不加 async 结果都一样,如果没明白,请回过头再去看看上面的“async 起什么作用”。

又一个疑问产生了,这两段代码,两种方式对异步调用的处理(实际就是对 Promise 对象的处理)差别并不明显,甚至使用 async/await 还需要多写一些代码,那它的优势到底在哪?

async/await 的优势在于处理 then 链

单一的 Promise 链并不能发现 async/await 的优势,但是,如果需要处理由多个 Promise 组成的 then 链的时候,优势就能体现出来了(很有意思,Promise 通过 then 链来解决多层回调的问题,现在又用 async/await 来进一步优化它)。

假设一个业务,分多个步骤完成,每个步骤都是异步的,而且依赖于上一个步骤的结果。我们仍然用 setTimeout 来模拟异步操作:

/**
 * 传入参数 n,表示这个函数执行的时间(毫秒)
 * 执行的结果是 n + 200,这个值将用于下一步骤
 */
function takeLongTime(n) {
    return new Promise(resolve => {
        setTimeout(() => resolve(n + 200), n);
    });
}

function step1(n) {
    console.log(`step1 with ${n}`);
    return takeLongTime(n);
}

function step2(n) {
    console.log(`step2 with ${n}`);
    return takeLongTime(n);
}

function step3(n) {
    console.log(`step3 with ${n}`);
    return takeLongTime(n);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

现在用 Promise 方式来实现这三个步骤的处理

function doIt() {
    console.time("doIt");
    const time1 = 300;
    step1(time1)
        .then(time2 => step2(time2))
        .then(time3 => step3(time3))
        .then(result => {
            console.log(`result is ${result}`);
            console.timeEnd("doIt");
        });
}

doIt();

// c:\var\test>node --harmony_async_await .
// step1 with 300
// step2 with 500
// step3 with 700
// result is 900
// doIt: 1507.251ms
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

输出结果 result 是 step3() 的参数 700 + 200 = 900。doIt() 顺序执行了三个步骤,一共用了 300 + 500 + 700 = 1500 毫秒,和 console.time()/console.timeEnd() 计算的结果一致。

如果用 async/await 来实现呢,会是这样

async function doIt() {
    console.time("doIt");
    const time1 = 300;
    const time2 = await step1(time1);
    const time3 = await step2(time2);
    const result = await step3(time3);
    console.log(`result is ${result}`);
    console.timeEnd("doIt");
}

doIt();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

结果和之前的 Promise 实现是一样的,但是这个代码看起来是不是清晰得多,几乎跟同步代码一样

还有更酷的

现在把业务要求改一下,仍然是三个步骤,但每一个步骤都需要之前每个步骤的结果。

function step1(n) {
    console.log(`step1 with ${n}`);
    return takeLongTime(n);
}

function step2(m, n) {
    console.log(`step2 with ${m} and ${n}`);
    return takeLongTime(m + n);
}

function step3(k, m, n) {
    console.log(`step3 with ${k}, ${m} and ${n}`);
    return takeLongTime(k + m + n);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

这回先用 async/await 来写:

async function doIt() {
    console.time("doIt");
    const time1 = 300;
    const time2 = await step1(time1);
    const time3 = await step2(time1, time2);
    const result = await step3(time1, time2, time3);
    console.log(`result is ${result}`);
    console.timeEnd("doIt");
}

doIt();

// c:\var\test>node --harmony_async_await .
// step1 with 300
// step2 with 800 = 300 + 500
// step3 with 1800 = 300 + 500 + 1000
// result is 2000
// doIt: 2907.387ms
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

除了觉得执行时间变长了之外,似乎和之前的示例没啥区别啊!别急,认真想想如果把它写成 Promise 方式实现会是什么样子?

function doIt() {
    console.time("doIt");
    const time1 = 300;
    step1(time1)
        .then(time2 => {
            return step2(time1, time2)
                .then(time3 => [time1, time2, time3]);
        })
        .then(times => {
            const [time1, time2, time3] = times;
            return step3(time1, time2, time3);
        })
        .then(result => {
            console.log(`result is ${result}`);
            console.timeEnd("doIt");
        });
}

doIt();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

有没有感觉有点复杂的样子?那一堆参数处理,就是 Promise 方案的死穴—— 参数传递太麻烦了,看着就晕! 
通过使用async来写的话, 代码就会简洁得多, 而且可读性也更强

转载自: 
作者: 边城 
原文地址: https://segmentfault.com/a/1190000007535316#articleHeader1

<think>首先,用户的问题是:“我想Understanding and using async/await in programming 请问Introduction to async await in programming and its usage”。这翻译成中文是“我想了解编程中的async/await,请问async/await在编程中的介绍及其使用”。 系统级指令要求: - 所有行内数学表达式使用$...$格式。 - 独立公式使用$$...$$格式并单独成段。 - LaTeX语法正确。 - 使用中文回答。 - 生成相关问题。 - 回答中引用的段落末尾自然地添加引用标识,例如[^1]。 - 回答结构清晰,帮助用户逐步解决问题,尽量保证回答真实可靠。 用户可见层要求: - 回答结构清晰,帮助用户逐步解决问题。 参考站内引用: - 引用[1]: 关于Async/Await的性能成本。 - 引用[2]: 关于回退机制的简要介绍。 我需要基于这些引用构建回答。用户的问题是关于async/await的介绍和使用,所以我应该聚焦在编程中的async/await概念,尤其是在C#或类似语言中,因为引用[1]提到了C#编译器。 回答结构: 1. **介绍async/await的概念**:解释什么是async/await,为什么需要它。 2. **基本用法**:展示代码示例。 3. **优点和缺点**:讨论其好处和潜在问题。 4. **引用相关部分**:自然地整合引用[1]和[^2]。 5. **生成相关问题**:在回答结束时添加相关问题部分。 确保使用中文回答。 步骤: - **介绍**:定义async/await。例如,async/await是一种用于处理异步操作的模式,避免阻塞主线程。 - **用法**:提供简单的代码示例。引用[1]中有一个C#示例,我可以基于它。 - **性能方面**:引用[1]讨论了性能成本,所以我应该提到这一点。 - **回退机制**:引用[^2]是关于回退机制的,可能用于错误处理,所以我可以在讨论错误处理时提及。 - **数学表达式**:用户问题中没有明显的数学内容,但如果有相关概念(如时间复杂性),我可以使用LaTeX。例如,在讨论性能时,如果有公式,使用$...$或$$...$$。 - 例如,如果提到时间复杂度,可以写成$O(n)$。 - **生成相关问题**:在回答末尾添加“相关问题”部分。 生成相关问题: - 基于主题,例如: 1. async/await在哪些语言中可用? 2. 如何处理async代码中的错误? 3. async/await的性能优化技巧? 4. 与Promise或其他异步模式的比较? 5. 实际应用场景? 引用标识: - 在回答中,当引用[1]的内容时,添加[^1]。 - 引用[2]添加[^2]。 最终回答结构: 1. 标题或引言:介绍主题。 2. 主体:分步骤解释。 3. 代码块:展示示例。 4. 讨论:包括优点、缺点、性能。 5. 相关问题部分。 现在,构建内容: - **引言**:Async/await是一种编程模式,用于简化异步代码的编写,避免回调地狱,提高可读性。 - **基本概念**:在C#中,async关键字修饰方法,await关键字用于等待异步操作完成。 - **代码示例**:使用引用[1]中的例子,但确保语法正确。 - 原引用代码:public static async Task<int> SumAsync(Task<int> a, Task<int> b, Task<int> c) { return Sum(await a, await b, await c); } - 我需要用中文解释。 - **性能**:提到async/await有性能成本,因为涉及状态机生成[^1]。 - **错误处理**:提到回退机制用于处理失败操作[^2]。 - **LaTeX**:如果没有直接数学,但假设在讨论时间,可以添加。例如,异步操作的延迟可能用$t$表示。 - 为了符合指令,我可以添加一个简单的公式,比如在讨论性能时,提到时间复杂度为$O(1)$或类似,但实际中可能不必要。指令说“如果包括”,所以如果没有,就不强制。 - **结束**:总结,并添加相关问题。 相关问题: 1. async/await如何影响应用程序的性能? 2. 在异步编程中如何处理异常? 3. async/await与其他异步模式(如Promise)有什么区别? 4. 在实际项目中,async/await的常见陷阱是什么? 5. 如何优化async/await代码以减少开销? 确保回答真实可靠:基于一般知识,引用提供的参考。</think>### Async/Await在编程中的介绍及其使用 Async/await是一种现代编程模式,用于简化异步操作的处理,避免传统的回调地狱(Callback Hell),提高代码的可读性和可维护性。它广泛应用于C#、JavaScript、Python等语言中,允许开发者以同步的方式编写异步代码。核心思想是:使用`async`关键字标记异步方法,`await`关键字等待异步操作完成,而不会阻塞主线程。这在处理I/O密集型任务(如网络请求、文件读写)时尤其重要,因为它能释放系统资源,提升应用程序的响应性和吞吐量。 #### 1. **基本概念与工作原理** - **async关键字**:用于修饰方法,表明该方法包含异步操作。被修饰的方法返回一个`Task`(在C#中)或`Promise`(在JavaScript中),表示异步操作的结果。 - **await关键字**:用于等待异步操作完成。它会暂停当前方法的执行,直到异步操作返回结果,但不会阻塞线程,允许其他任务继续运行。 - **优势**:与传统回调或线程相比,async/await代码更易读、更少嵌套。例如,它避免了多层回调的复杂性,使错误处理更直观。 - **数学表示**:在性能分析中,async/await的开销可建模为状态机生成的成本。假设异步操作的平均延迟为$t$毫秒,使用async/await后,总等待时间可近似为$T = n \times t$(其中$n$是操作数),但实际中因上下文切换和调度而略有增加[^1]。 #### 2. **基本用法与代码示例** 以下以C#语言为例,展示async/await的典型用法。C#编译器支持在几乎所有表达式中使用`await`,包括作为更大表达式的一部分,这提高了灵活性[^1]。 ```csharp // 定义一个异步方法,计算三个任务结果的和 public static async Task<int> SumAsync(Task<int> a, Task<int> b, Task<int> c) { // 使用await等待每个任务完成,并获取结果 int resultA = await a; int resultB = await b; int resultC = await c; return Sum(resultA, resultB, resultC); // 调用同步辅助方法 } private static int Sum(int a, int b, int c) { return a + b + c; // 简单的同步计算 } ``` **解释**: - `SumAsync`方法被标记为`async`,返回`Task<int>`。 - `await a`、`await b`、`await c`会依次等待每个任务完成,但执行是非阻塞的:如果某个任务未完成,控制权会返回给调用方,允许其他代码运行。 - 在JavaScript或Python中,语法类似,但关键字和返回类型略有不同(如JavaScript使用`async/await`与`Promise`)。 这个示例展示了async/await如何简化异步代码:它看起来像同步代码,但内部是异步执行的。实际应用中,这常用于数据库查询或API调用。 #### 3. **优点与潜在问题** - **优点**: - **可读性高**:代码结构清晰,减少嵌套。 - **资源高效**:避免线程阻塞,提升并发性能,特别适合I/O密集型应用。 - **错误处理简化**:可使用`try/catch`块捕获异常,与同步代码一致。 - **缺点与注意事项**: - **性能开销**:async/await涉及状态机生成和上下文切换,可能引入额外成本。在C#中,编译器会为每个`async`方法生成一个状态机,增加内存和CPU使用[^1]。对于高性能场景,需谨慎使用。 - **死锁风险**:如果在UI线程或同步上下文中错误使用`await`,可能导致死锁。解决方案是使用`ConfigureAwait(false)`避免上下文捕获。 - **错误处理扩展**:对于可能失败的操作,应结合回退机制(如指数退避),以重试失败的任务[^2]。例如,网络请求失败时,自动重试并逐步增加延迟。 #### 4. **实际应用场景** - **Web开发**:在ASP.NET Core中处理HTTP请求时,使用async/await异步访问数据库,提升服务器吞吐量。 - **移动应用**:在Android或iOS开发中,异步加载网络数据,防止UI冻结。 - **桌面应用**:在WPF或WinForms中,后台执行耗时操作,保持界面响应。 - **云服务**:Azure Functions或AWS Lambda中,处理事件驱动的异步任务。 总之,async/await是现代异步编程的核心工具,它通过简化代码结构显著提升开发效率。但需注意性能开销和正确使用模式,以避免常见陷阱[^1][^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值