俄罗斯贵宾会-俄罗斯贵宾会官网
做最好的网站

俄罗斯贵宾会用图表和实例解释 Await 和 Async

打赏支持我翻译更多好文章,谢谢!

任选一种支付方式

俄罗斯贵宾会 1 俄罗斯贵宾会 2

1 赞 3 收藏 评论

JavaScript 之旅

异步编程是 JavaScript 无法避免的挑战。回调在大多数应用中是必不可少的,但是容易陷入深度嵌套的函数中。

Promise 抽象了回调,但是有许多句法陷阱。转换已有函数可能是一件苦差事,·then() 链式调用看起来很凌乱。

很幸运,async/await 表达清晰。代码看起来是同步的,但是又不独占单个处理线程。它将改变你书写 JavaScript 的方式,甚至让你更赏识 Promise – 如果没接触过的话。

1 赞 收藏 评论

俄罗斯贵宾会 3

错误处理

前面大部分例子中,我们都假设 promise 执行成功。因此在 promise 上使用 await 会返回值。如果我们进行 await 的 promise 失败了,async 函数就会发生异常。我们可以用标准的 try / catch 来处理这种情况:

async function f() { try { const promiseResult = await Promise.reject('Error'); } catch (e){ console.log(e); } }

1
2
3
4
5
6
7
async function f() {
    try {
        const promiseResult = await Promise.reject('Error');
    } catch (e){
        console.log(e);
    }
}

Async 函数不会处理异常,不管异常是由拒绝的 promise 还是其他 bug 引起的,它都会返回一个拒绝 promise:

async function f() { // Throws an exception const promiseResult = await Promise.reject('Error'); } // Will print "Error" f(). then(() => console.log('Success')). catch(err => console.log(err)) async function g() { throw "Error"; } // Will print "Error" g(). then(() => console.log('Success')). catch(err => console.log(err))

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
async function f() {
    // Throws an exception
    const promiseResult = await Promise.reject('Error');
}
 
// Will print "Error"
f().
    then(() => console.log('Success')).
    catch(err => console.log(err))
 
async function g() {
    throw "Error";
}
 
// Will print "Error"
g().
    then(() => console.log('Success')).
    catch(err => console.log(err))

这让我们能得心应手地通过熟悉的异常处理机制来处理拒绝的 promise.

Async/Await

Promise 看起来有点复杂,所以 ES2017 引进了 asyncawait。虽然只是语法糖,却使 Promise 更加方便,并且可以避免 .then() 链式调用的问题。看下面使用 Promise 的例子:

function connect() { return new Promise((resolve, reject) => { asyncDBconnect('http://localhost:1234') .then(asyncGetSession) .then(asyncGetUser) .then(asyncLogAccess) .then(result => resolve(result)) .catch(err => reject(err)) }); } // 运行 connect 方法 (自执行方法) (() => { connect(); .then(result => console.log(result)) .catch(err => console.log(err)) })();

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function connect() {
 
  return new Promise((resolve, reject) => {
 
    asyncDBconnect('http://localhost:1234')
      .then(asyncGetSession)
      .then(asyncGetUser)
      .then(asyncLogAccess)
      .then(result => resolve(result))
      .catch(err => reject(err))
 
  });
}
 
// 运行 connect 方法 (自执行方法)
(() => {
  connect();
    .then(result => console.log(result))
    .catch(err => console.log(err))
})();

使用 async / await 重写上面的代码:

  1. 外部方法用 async 声明
  2. 基于 Promise 的异步方法用 await 声明,可以确保下一个命令执行前,它已执行完成

async function connect() { try { const connection = await asyncDBconnect('http://localhost:1234'), session = await asyncGetSession(connection), user = await asyncGetUser(session), log = await asyncLogAccess(user); return log; } catch (e) { console.log('error', err); return null; } } // 运行 connect 方法 (自执行异步函数) (async () => { await connect(); })();

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
async function connect() {
 
  try {
    const
      connection = await asyncDBconnect('http://localhost:1234'),
      session = await asyncGetSession(connection),
      user = await asyncGetUser(session),
      log = await asyncLogAccess(user);
 
    return log;
  }
  catch (e) {
    console.log('error', err);
    return null;
  }
 
}
 
// 运行 connect 方法 (自执行异步函数)
(async () => { await connect(); })();

await 使每个异步调用看起来像是同步的,同时不耽误 JavaScript 的单线程处理。此外,async 函数总是返回一个 Promise 对象,因此它可以被其他 async 函数调用。

async / await 可能不会让代码变少,但是有很多优点:

  1. 语法更清晰。括号越来越少,出错的可能性也越来越小。
  2. 调试更容易。可以在任何 await 声明处设置断点。
  3. 错误处理尚佳。try / catch 可以与同步代码使用相同的处理方式。
  4. 支持良好。所有浏览器(除了 IE 和 Opera Mini )和 Node7.6+ 均已实现。

如是说,没有完美的…

Async 方法

Async 是定义返回 promise 对象函数的快捷方法。

例如,下面这两种定义是等价的:

function f() { return Promise.resolve('TEST'); } // asyncF 和 f 是等价的 async function asyncF() { return 'TEST'; }

1
2
3
4
5
6
7
8
function f() {
    return Promise.resolve('TEST');
}
 
// asyncF 和 f 是等价的
async function asyncF() {
    return 'TEST';
}

类似地,抛出异常的 async 方法等价于返回拒绝 promise 的方法:

function f() { return Promise.reject('Error'); } // asyncF 和 f 是等价的 async function asyncF() { throw 'Error'; }

1
2
3
4
5
6
7
8
function f() {
    return Promise.reject('Error');
}
 
// asyncF 和 f 是等价的
async function asyncF() {
    throw 'Error';
}

使用 Promise.all() 处理多个异步操作

Promise .then() 方法用于相继执行的异步函数。如果不关心顺序 – 比如,初始化不相关的组件 – 所有异步函数同时启动,直到最慢的函数执行 resolve,整个流程结束。

Promise.all() 适用于这种场景,它接收一个函数数组并且返回另一个 Promise。举例:

Promise.all([ async1, async2, async3 ]) .then(values => { // 返回值的数组 console.log(values); // (与函数数组顺序一致) return values; }) .catch(err => { // 任一 reject 被触发 console.log('error', err); });

1
2
3
4
5
6
7
8
Promise.all([ async1, async2, async3 ])
  .then(values => {           // 返回值的数组
    console.log(values);      // (与函数数组顺序一致)
    return values;
  })
  .catch(err => {             // 任一 reject 被触发
    console.log('error', err);
  });

任意一个异步函数 rejectPromise.all() 会立即结束。

用图表和实例解释 Await 和 Async

2018/08/13 · JavaScript · async, await, Promise, 异步

本文由 伯乐在线 - 王浩 翻译,艾凌风 校稿。未经许可,禁止转载!
英文出处:Nikolay Grozev。欢迎加入翻译组

Promises, Promises

async / await 仍然依赖 Promise 对象,最终依赖回调。你需要理解 Promise 的工作原理,它也并不等同于 Promise.all()Promise.race()。比较容易忽视的是 Promise.all(),这个命令比使用一系列无关的 await 命令更高效。

问题来了——组合 promise

只用一个 promise 很容易搞定。但是,当需要针对复杂异步逻辑编程时,我们很可能最后要同时用好几个 promise 对象。写一堆 then 语句和匿名回调很容易搞得难以控制。

例如,假设我们需要编程解决如下需求:

  1. 创建 HTTP 请求,等待请求结束并打印出结果;
  2. 再创建两个并行 HTTP 请求;
  3. 等这两个请求结束后,打印出它们的结果。

下面这段代码示范了如何解决此问题:

// 第一次调用 const call1Promise = rp('http://example.com/'); call1Promise.then(result1 => { // 第一个请求完成后会执行 console.log(result1); const call2Promise = rp('http://example.com/'); const call3Promise = rp('http://example.com/'); return Promise.all([call2Promise, call3Promise]); }).then(arr => { // 两个 promise 都结束后会执行 console.log(arr[0]); console.log(arr[1]); })

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 第一次调用
const call1Promise = rp('http://example.com/');
 
call1Promise.then(result1 => {
    // 第一个请求完成后会执行
    console.log(result1);
    const call2Promise = rp('http://example.com/');
    const call3Promise = rp('http://example.com/');
 
    return Promise.all([call2Promise, call3Promise]);
}).then(arr => {
    // 两个 promise 都结束后会执行
    console.log(arr[0]);
    console.log(arr[1]);
})

我们开头创建了第一个 HTTP 请求,并且加了个完成时候运行的回调(1-3行)。在这个回调函数里,我们为随后的 HTTP 请求创建了另外两个 promise(8-9行)。这两个 promise 同时执行,我们需要加一个能等它们都完成后才执行的回调函数。因此,我们需要用 Promise.all 将它们组合到同一个 promise 中(11 行),它们都结束后这个 promise 才算完成。这个回调返回的是 promise 对象,所以我们要再加一个 then 回调函数来打印结果(12-16行)。

下图描述了这一计算流程:

俄罗斯贵宾会 4

Promise 组合的计算过程。我们用  Promise.all 将两个并行的 promise 组合到一个 promise 中。

对于这个简单的例子,我们最后用了两个 then 回调方法,并且不得不用 Promise.all 来让两个并行的 promise 同时执行。如果我们必须执行更多异步操作,或者加上错误处理会怎么样呢?这种方法最后很容易产生一堆乱七八糟的 then, Promise.all 和回调函数。

丑陋的 try/catch

如果执行失败的 await 没有包裹 try / catchasync 函数将静默退出。如果有一长串异步 await 命令,需要多个 try / catch 包裹。

替代方案是使用高阶函数来捕捉错误,不再需要 try / catch 了(感谢@wesbos的建议):

async function connect() { const connection = await asyncDBconnect('http://localhost:1234'), session = await asyncGetSession(connection), user = await asyncGetUser(session), log = await asyncLogAccess(user); return true; } // 使用高阶函数捕获错误 function catchErrors(fn) { return function (...args) { return fn(...args).catch(err => { console.log('ERROR', err); }); } } (async () => { await catchErrors(connect)(); })();

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
async function connect() {
 
  const
    connection = await asyncDBconnect('http://localhost:1234'),
    session = await asyncGetSession(connection),
    user = await asyncGetUser(session),
    log = await asyncLogAccess(user);
 
  return true;
}
 
// 使用高阶函数捕获错误
function catchErrors(fn) {
  return function (...args) {
    return fn(...args).catch(err => {
      console.log('ERROR', err);
    });
  }
}
 
(async () => {
  await catchErrors(connect)();
})();

当应用必须返回区别于其它的错误时,这种作法就不太实用了。

尽管有一些缺陷,async/await 还是 JavaScript 非常有用的补充。更多资源:

简介

JavaScript ES7 中的 async / await 让多个异步 promise 协同工作起来更容易。如果要按一定顺序从多个数据库或者 API 异步获取数据,你可能会以一堆乱七八糟的 promise 和回调函数而告终。而 async / await 结构让我们能用可读性强、易维护的代码更加简洁地实现这些逻辑。

本教程用图表和简单示例讲解了 JavaScript 中 async / await 的语法和语义。

在深入之前,我们先简单回顾一下 promise. 如果你已经对 JS 的 promise 有所了解,可放心大胆地跳过这一部分。

Promises

ES2015(ES6) 引入了 Promises。回调函数依然有用,但是 Promises 提供了更清晰的链式异步命令语法,因此可以串联运行(下个章节会讲)。

打算基于 Promise 封装,异步回调函数必须返回一个 Promise 对象。Promise 对象会执行以下两个函数(作为参数传递的)其中之一:

  • resolve:执行成功回调
  • reject:执行失败回调

以下例子,database API 提供了一个 connect() 方法,接收一个回调函数。外部的 asyncDBconnect() 函数立即返回了一个新的 Promise,一旦连接创建成功或失败,resolve()reject() 便会执行:

const db = require('database'); // 连接数据库 function asyncDBconnect(param) { return new Promise((resolve, reject) => { db.connect(param, (err, connection) => { if (err) reject(err); else resolve(connection); }); }); }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const db = require('database');
 
// 连接数据库
function asyncDBconnect(param) {
 
  return new Promise((resolve, reject) => {
 
    db.connect(param, (err, connection) => {
      if (err) reject(err);
      else resolve(connection);
    });
 
  });
 
}

Node.js 8.0 以上提供了 util.promisify() 功能,可以把基于回调的函数转换成基于 Promise 的。有两个使用条件:

  1. 传入一个唯一的异步函数
  2. 传入的函数希望是错误优先的(比如:(err, value) => …),error 参数在前,value 随后

举例:

// Node.js: 把 fs.readFile promise 化 const util = require('util'), fs = require('fs'), readFileAsync = util.promisify(fs.readFile); readFileAsync('file.txt');

1
2
3
4
5
6
7
// Node.js: 把 fs.readFile promise 化
const
  util = require('util'),
  fs = require('fs'),
  readFileAsync = util.promisify(fs.readFile);
 
readFileAsync('file.txt');

各种库都会提供自己的 promisify 方法,寥寥几行也可以自己撸一个:

// promisify 只接收一个函数参数 // 传入的函数接收 (err, data) 参数 function promisify(fn) { return function() { return new Promise( (resolve, reject) => fn( ...Array.from(arguments), (err, data) => err ? reject(err) : resolve(data) ) ); } } // 举例 function wait(time, callback) { setTimeout(() => { callback(null, 'done'); }, time); } const asyncWait = promisify(wait); ayscWait(1000);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// promisify 只接收一个函数参数
// 传入的函数接收 (err, data) 参数
function promisify(fn) {
  return function() {
      return new Promise(
        (resolve, reject) => fn(
          ...Array.from(arguments),
        (err, data) => err ? reject(err) : resolve(data)
      )
    );
  }
}
 
// 举例
function wait(time, callback) {
  setTimeout(() => { callback(null, 'done'); }, time);
}
 
const asyncWait = promisify(wait);
 
ayscWait(1000);

讨论

Async / await 是让 promise 更完美的语言结构。它让我们能用更少的代码使用 promise. 然而,async / await 并没有取代普通 promise. 例如,如果在普通函数中或者全局范围内调用 async 函数,我们就没办法使用 await 而要依赖于普通 promise:

async function fAsync() { // actual return value is Promise.resolve(5) return 5; } // can't call "await fAsync()". Need to use then/catch fAsync().then(r => console.log(`result is ${r}`));

1
2
3
4
5
6
7
async function fAsync() {
    // actual return value is Promise.resolve(5)
    return 5;
}
 
// can't call "await fAsync()". Need to use then/catch
fAsync().then(r => console.log(`result is ${r}`));

我通常会将大部分异步逻辑封装到一个或者几个 async 函数中,然后在非异步代码中调用。这让我尽可能少地写 try / catch 回调。

Async / await 结构是让使用 promise 更简练的语法糖。每一个 async / await 结构都可以写成普通 promise. 归根结底,这是一个编码风格和简洁的问题。

关于说明并发并行有区别的资料,可以查看 Rob Pike 关于这个问题的讨论,或者我这篇文章。并发是指将独立进程(通常意义上的进程)组合在一起工作,而并行是指真正同时处理多个任务。并发关乎应用设计和架构,而并行关乎实实在在的执行。

我们拿一个多线程应用来举例。应用程序分离成线程明确了它的并发模型。这些线程在可用内核上的映射定义了其级别或并行性。并发系统可以在单个处理器上高效运行,在这种情况下,它并不是并行的。

俄罗斯贵宾会 5

并发VS并行

从这个意义上说,promise 让我们能够将程序分解成并发模块,这些模块可能会也可能不会并行执行。Javascript 实际否并行执行取决于具体实现方法。例如,Node JS 是单线程的,如果 promise 是计算密集型(CPU bound)那就不会有并行处理。但是,如果你用 Nashorn 之类的东西把代码编译成 java 字节码,理论上可能能够将计算密集型的 promise 映射到不同 CPU 核上,从而达到并行效果。所以我认为,promise(不管是普通的还是用了 async / await 的)组成了 JavaScript 应用的并发模块。

打赏支持我翻译更多好文章,谢谢!

打赏译者

异步链式调用

任何返回 Promise 的函数都可以通过 .then() 链式调用。前一个 resolve 的结果会传递给后一个:

asyncDBconnect('http://localhost:1234') .then(asyncGetSession) // 传递 asyncDBconnect 的结果 .then(asyncGetUser) // 传递 asyncGetSession 的结果 .then(asyncLogAccess) // 传递 asyncGetUser 的结果 .then(result => { // 同步函数 console.log('complete'); // (传递 asyncLogAccess 的结果) return result; // (结果传给下一个 .then()) }) .catch(err => { // 任何一个 reject 触发 console.log('error', err); });

1
2
3
4
5
6
7
8
9
10
11
asyncDBconnect('http://localhost:1234')
  .then(asyncGetSession)      // 传递 asyncDBconnect 的结果
  .then(asyncGetUser)         // 传递 asyncGetSession 的结果
  .then(asyncLogAccess)       // 传递 asyncGetUser 的结果
  .then(result => {           // 同步函数
    console.log('complete');  //   (传递 asyncLogAccess 的结果)
    return result;            //   (结果传给下一个 .then())
  })
  .catch(err => {             // 任何一个 reject 触发
    console.log('error', err);
  });

同步函数也可以执行 .then(),返回的值传递给下一个 .then()(如果有)。

当任何一个前面的 reject 触发时,.catch() 函数会被调用。触发 reject 的函数后面的 .then() 也不再执行。贯穿整个链条可以存在多个 .catch() 方法,从而捕获不同的错误。

ES2018 引入了 .finally() 方法,它不管返回结果如何,都会执行最终逻辑 – 例如,清理操作,关闭数据库连接等等。当前仅有 Chrome 和 Firefox 支持,但是 TC39 技术委员会已经发布了 .finally() 补丁

function doSomething() { doSomething1() .then(doSomething2) .then(doSomething3) .catch(err => { console.log(err); }) .finally(() => { // 清理操作放这儿! }); }

1
2
3
4
5
6
7
8
9
10
11
function doSomething() {
  doSomething1()
  .then(doSomething2)
  .then(doSomething3)
  .catch(err => {
    console.log(err);
  })
  .finally(() => {
    // 清理操作放这儿!
  });
}

本文由俄罗斯贵宾会发布于Web前端,转载请注明出处:俄罗斯贵宾会用图表和实例解释 Await 和 Async

您可能还会对下面的文章感兴趣: