使用async/await与promises有何区别?

30

我正在寻找一种在我的nodeJS应用程序中使用的解决方案。

我有处理通用dB访问到mssql的代码。这个代码是使用async函数编写的,然后我使用promise调用该函数,一切都很顺利。

随着我的应用程序变得越来越大、代码越来越多,我计划将一些逻辑移入函数中并调用这些函数。

所以我的问题是:在使用async/await和promise混合时是否存在缺点,或者真的没有关系?

使用async/await可以使代码更易读,因为我必须在返回之前读取和写入多个dB,并且我需要其中一些的结果。

那么问题是什么才是更好的方法? 在dB层上使用async/await,这是固定的,无法更改 逻辑层async/await,这将允许我在函数调用上使用async/await,或者如果我选择在逻辑上使用promise,则在函数调用上我将被束缚于promise。

所以我希望有人能够给我更多的见解,哪个比其他一个更有优势,除了能够编写更干净的代码之外。


本文强调了使用async/await相比于Promise的一些优势。但我很想知道在使用其中一种方法时是否有性能上的提升。 - Akber Iqbal
@Alqbal,使用async/await并没有性能上的提升。 - XaxD
5
async/await 只能与 promises 一起使用,因此不存在 async/await 或 promises 的选择,只有 async/await 和 promises 的结合。因此,你真正需要问的是是否只使用普通的 promises,还是使用带有 async/await 的 promises。 - jfriend00
1
能否澄清一件重要的事情?async / await与promise有关系,因此,按照字面意义问问题是误导性的。await语句等待promise被满足。你真正想知道的是使用awaitthen()方法的区别。简短回答是它们大部分的工作都相同,所以这主要取决于个人喜好。 - Manngo
5个回答

57

async/await和promises密切相关。 async函数返回promises,而await是用于等待promise被解决的语法糖。

混合使用promises和async函数可能唯一的缺点是代码的可读性和可维护性,但你可以将async函数的返回值视为promise以及对返回promise的常规函数使用await

您选择其中一个的大部分取决于可用性(您的node.js /浏览器是否支持async?)和美学偏好,但根据我的建议(基于我写作时的偏好),一个好的经验法则可能是:

如果您需要按顺序运行异步代码:请考虑使用async/await

return asyncFunction()
.then(result => f1(result))
.then(result2 => f2(result2));

对比

const result = await asyncFunction();
const result2 = await f1(result);
return await f2(result2);

如果你需要嵌套的 Promises:使用 async/await

return asyncFunction()
.then(result => {
  return f1(result)
  .then(result2 => f2(result, result2);
})

对比

const result = await asyncFunction();
const result2 = await f1(result);
return await f2(result, result2);

如果需要并行运行: 使用Promise。

return Promise.all(arrayOfIDs.map(id => asyncFn(id)))

有人建议可以在表达式中使用 await 来等待多个任务,例如:
*注意,这仍按顺序从左到右等待,如果您不期望出现错误,这是可以的。否则,由于 Promise.all() 的快速失败行为 ,行为将不同。

const [r1, r2, r3] = [await task1, await task2, await task3];

(async function() {
  function t1(t) {
    console.time(`task ${t}`);
    console.log(`start task ${t}`);
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        console.timeEnd(`task ${t}`);
        resolve();
      }, t);
    })
  }

  console.log('Create Promises');
  const task1 = t1(100);
  const task2 = t1(200);
  const task3 = t1(10);

  console.log('Await for each task');
  const [r1, r2, r3] = [await task1, await task2, await task3];

  console.log('Done');
}())

但是,就像使用Promise.all一样,在出现错误时需要正确处理并行的 Promise。您可以在这里阅读更多相关内容。

请注意,不要将上述代码与以下代码混淆:

let [r1, r2] = [await t1(100), await t2(200)];

function t1(t) {
  console.time(`task ${t}`);
  console.log(`start task ${t}`);
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.timeEnd(`task ${t}`);
      resolve();
    }, t);
  })
}
console.log('Promise');
Promise.all([t1(100), t1(200), t1(10)]).then(async() => {

  console.log('Await');
  let [r1, r2, r3] = [await t1(100), await t1(200), await t1(10)]
});

使用这两种方法并不等同。了解更多区别

最终,Promise.all是一种更干净、适用于任意数量任务的扩展性更好的方法。


实际上你可以使用 await 并行运行代码,例如 let [r1, r2] = [await task1, await task2]。请查看 https://dev59.com/I1sV5IYBdhLWcg3w8imr#53345172。 - Qwerty
@Qwerty 谢谢您的反馈!我不确定那是否正确。请检查我的编辑和链接的答案。那能理解吗?如果我错了,请告诉我! - lucascaro
@lucascaro 我有一个疑问。相比使用 then,不是 await 会让代码阻塞吗? - Mayank Raj
从某种意义上讲,你可以这样想,await会“暂停”当前函数,直到Promise被解决,但由于该函数是异步的,它不会阻塞当前函数之外的任何东西。 - lucascaro
1
你的代码并没有反映出@Qwerty所建议的(如果你遵循链接,它会更清晰)。task1task2是promises,因此它们已经在并行运行。而不是[await t1(100), await t1(200), await t1(10)],你需要在使用await之前先调用每个t1()。冗长的方法是let task1 = t1(100); let task2 = t1(200); let task3 = t1(10); let [r1, r2, r3] = [await task1, await task2, await task3];。但在async/await代码中使用Promise.all是有意义的,因为await只是期望一个Promiselet [r1, r2, r3] = await Promise.all([t1(100), t1(200), t1(10)]); - Zantier

6

实际上这取决于您的节点版本,但如果您可以使用async/await,那么您的代码将更易读和更易维护。 当您将函数定义为“async”时,它会返回本机Promise,当您使用await调用它时,它会执行Promise.then。

注意: 将await调用放在try/catch中,因为如果Promise失败,则会发出'catch',您可以在catch块中处理它。

try{
let res1 = await your-async-function(parameters);
let res2 = await your-promise-function(parameters);
await your-async-or-promise-function(parameters);
}
catch(ex){
// your error handler goes here
// error is caused by any of your called functions which fails its promise
// this methods breaks your call chain
}

您也可以像这样处理您的'catch':
let result = await your-asyncFunction(parameters).catch((error)=>{//your error handler goes here});

这种方法不会产生异常,因此执行会继续。

我认为async/await与原生Promise模块实现之间没有任何性能差异。

我建议使用bluebird模块代替内置于Node的原生Promise。


5

目前使用Promises的唯一原因是通过Promise.all()调用多个异步作业,否则通常最好使用async/await或Observables。


1
你可以在 async/await 中使用 Promise.all()const [p1, p2, p3] = await Promise.all([p1(), p2(), p3()]); - Asaf Aviv
p1、p2、p3 都是该实例中的经典 Promise。如果你分别等待它们,它们将会同步执行。 - XaxD
它们都是并发执行的。我们在调用下一个之前不等待其中一个完成。我们只需使用Promise.all()等待所有任务完成。 - Asaf Aviv
没错。但是如果你调用 await p1(),await p2(),await p3(),它们就会并发执行。p1-p3 必须声明为显式的 Promise。 - XaxD
3
如果解释为什么或如何使用async/await或Observables更好,那么这个答案会更有帮助。按照现在的写法,对于那些不熟悉这些主题的人来说,它似乎并不是很有用。 - Asker
@Asker 这是对lucascaro答案的简短概括,而他的答案则更为详尽。 - XaxD

3

取决于你擅长哪种方法,Promise和async/await都是不错的选择。但如果你想编写异步代码,却又使用同步代码结构,那么应该采用async/await方法。像下面这个例子,在返回用户的函数中既可以使用Promise风格,也可以使用async/await风格。

如果我们使用Promise:

function getFirstUser() {
    return getUsers().then(function(users) {
        return users[0].name;
    }).catch(function(err) {
        return {
          name: 'default user'
        };
    });
}

如果我们使用async/await

async function getFirstUser() {
    try {
        let users = await getUsers();
        return users[0].name;
    } catch (err) {
        return {
            name: 'default user'
        };
    }
}

在 Promise 方法中,我们需要遵循一个可“thenable”结构,在 async/await 方法中,我们使用 'await' 来暂停执行异步函数。
如果您需要更多的解释,请查看此链接:https://medium.com/@bluepnume/learn-about-promises-before-you-start-using-async-await-eb148164a9c8

-2
昨天我做出了一个试探性的决定,打算从使用Promises转为使用Async/Await,独立于nodejs,主要基于在Promise链中访问先前的值时遇到的困难。我确实想出了一种使用'bind'在'then'函数内保存值的简洁解决方案,但是Async似乎更好(而且确实如此),因为它允许直接访问局部变量和参数。当然,Async/Await更明显的优点是,摒弃了复杂的显式'then'函数,而采用了类似普通函数调用的线性表示法。
然而,我今天阅读的内容揭示了Async/Await存在的问题,这使得我的决定受到了干扰。我想我会坚持使用Promises(可能使用宏预处理器来简化'then'函数的外观),直到过几年后Async/Await得到修复。
以下是我发现的问题。我希望发现自己是错的,这些问题有简单的解决方案。
  1. 需要一个外部的 try/catch 或者一个最终的 Promise.catch(),否则错误和异常将会丢失。

  2. 一个最终的 await 需要一个 Promise.then() 或者额外的外部 async 函数。

  3. 只能使用 for/of 来正确地进行迭代,不能使用其他迭代器。

  4. Await 只能同时等待一个 Promise,不能等待并行的 Promise,比如使用 Promise.all 进行 Promise 链。

  5. Await 不支持 Promise.race(),如果需要的话。


使用 Promises 和 Async Await,1 都代表 true。 - XaxD
你能详细说明第三个吗? - XaxD
我基于自己曾经阅读过的某篇文章,写下了#3。但现在我已经找不到那篇文章了。实际上,来自 Jake Archibald 的 https://jakearchibald.com/2017/async-iterators-and-generators/ 这篇关于 Chrome 浏览器的文章发布时间已有近 3 年,其中指出 async 和 iterators 结合使用没有问题。但我不知道这篇文章是否仍然适用于 Chrome 和其他浏览器,并且我也没有时间去尝试这项异步技术。 - David Spector

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接