使用async await和.then一起的方法

80

同时使用 async/await.then().catch() 有什么害处吗,比如:

async apiCall(params) {
    var results = await this.anotherCall()
      .then(results => {
        //do any results transformations
        return results;
      })
      .catch(error => {
        //handle any errors here
      });
    return results;
  }

3
不,没有任何危害。你只是使用await将Promise链的最终值传递给result变量。 - yqlim
11个回答

70

我经常使用async/await.catch()来使代码更加紧凑,而不是使用async/awaittry/catch

async function asyncTask() {
  throw new Error('network')
}
async function main() {
  const result = await asyncTask().catch(error => console.error(error));
  console.log('result:', result)
}

main();

如果您想在出现错误时获得备用值,您可以忽略错误,并在 .catch() 方法中返回一个值。

async function asyncTask() {
  throw new Error('network')
}
async function main() {
  const result = await asyncTask().catch(_ => 'fallback value');
  console.log('result:', result)
}

main();


1
假设在result =行之后还有更多的代码。假设出现了错误。那么result是未定义的吗? - Jeff Padgett
@JeffPadgett 是的,你可以从.catch()方法返回一个备用值。 - Lin Du
1
@JeffPadgett 已更新答案。 - Lin Du
非常好而且紧凑,回退值是避免Typescript错误的很好的方法。 - ndh103
1
你的代码在使用await时没有包含任何then子句,这正是问题所在! - S.Serpooshan
显示剩余3条评论

45

不想唤起死者,但想指出使用awaitthen链结合使用意味着:

const x = await someAsyncFn().then(() => doSomeLogging());

变量x被赋值为.then的返回值(如果doSomeLogging是无返回值,则为undefined),这对我来说并不是很直观。


2
如果您将在 .then 中等待值,那么添加 await 的意义何在?代码如何工作?变量 x 的值将是多少? - Mohammed Shahed
2
@MohammedShahed,这并没有什么意义,只是如果人们对这两种范式都不是很熟悉,可能会这样做。关键在于这两种不同的方法基本上做相同的事情,但它们并不总是能很好地协作。 - Bricky
实际上有一个很好的观点,与分解和模块化有关。可重用的异步调用可以在不同的上下文中共享,因此您希望在一个上下文中使用promise.then(),在另一个上下文中使用await。这样,您的API模块可以执行许多后处理数据的操作,而调用者只需await最终的promise,无需了解具体细节。 - undefined

27

异步函数可以包含一个await表达式,它可以暂停异步函数的执行,并等待传递的Promise解决,然后恢复异步函数的执行并返回解决的值。

从下面的示例中可以看出,您可以使用两种方式处理等待结果和错误。关键字 await 使 JavaScript 等待直到该 promise 解决,并返回其结果(即您从已解决的 promise 中获得的结果)。因此,并没有什么危害(我不完全理解您在这里所指的危害是什么)。

function returnpromise(val) {
  return new Promise((resolve, reject) => {
    if (val > 5) {
      resolve("resolved"); // fulfilled
    } else {
      reject("rejected"); // rejected
    }
  });
}

//This is how you handle errors in await
async function apicall() {
  try {
    console.log(await returnpromise(5))
  } catch (error) {
    console.log(error)
  }
}

async function apicall2() {
  let data = await returnpromise(2).catch((error) => {
    console.log(error)
  })
}

apicall2();
apicall();

更多参考请查看-MDN文档


我看来,Harm的意思是:解释器是否抛出异常?这是不好的代码吗?—— 不是。 - WoodrowShigeru

12

我认为混合使用async/await和then不是一个好主意,特别是当你专注于异步函数的结果时,混合使用会带来一些风险,可能会悄悄地扭曲你的结果。

例子:

const res = await fetchData().then(()=>{return "something else"}).catch((err)=>{});

console.log(res); // not expected res data but something else

所以,混合使用是很危险的,顺便说一下,它会对阅读代码造成伤害。


2
建议:添加代码框和语言标识符以突出显示代码并使其更易读。 - I_love_vegetables
2
我同意混合使用 awaitthen 会影响可读性,但我不理解你在这里的例子。 - ggorlen

10
如果您使用Async/await,就不需要链式调用.then()方法,只需将resolve()返回的结果存储在变量中(例如示例中的response)。但是,如果要处理错误,您需要使用try/catch语句包裹您的代码:
async function f() {

  try {
    let response = await fetch('http://no-such-url');
  } catch(err) {
    alert(err); // TypeError: failed to fetch
  }
}

在你的Promise中使用: throw new Error("oops!"); 或者 Promise.reject(new Error("Whoops!"));

我一直在使用这个,它比使用.then和另一个匿名的异步函数更好,后者会使整个代码难以理解。 - scavenger
你这里不一定需要使用 try/catch。你可以使用 .then().catch() 以及 await。问题是这样做是否好。 - ggorlen

6
我认为在这个讨论中存在一些关于“伤害”含义的混淆。如果你将伤害定义为(仅仅)“OP的代码是否正常运行?”,那么它并不会造成伤害。
然而,如果你将伤害定义为“这是一个难以阅读、容易出错的反模式,往往会导致错误,并且从来不是真正必要的”,那么它确实是有害的。
在Stack Overflow上有无数个问题,其中OP混合使用.then和await,结果导致了bug。我在本文底部选择了一些问题进行了列举。
作为一个简单的经验法则,在一个函数中永远不要同时使用await和then。最好的情况下,这样做比只使用其中一个更难阅读,最糟糕的情况下,它隐藏了一个bug,通常与错误处理相关。
一般来说,优先选择async/await而不是.then。如果你在确定何时使用哪种方法方面遇到困难,可以进一步避免使用.then和.catch。
说到这一点,我喜欢偶尔使用.then,当涉及错误处理并且没有必要访问无法直接通过链传递的对象上的状态时,它可以更简洁一些。
fetch("https://www.example.com")
  .then(response => {
     if (!response.ok) {
       throw Error(response.statusText);
     }

     return response.json();
   )
  .then(data => console.log(data))
  .catch(err => console.error(err));

对我来说,这个看起来比较干净:

(async () => {
  try {
    const response = await fetch("https://www.example.com");
  
    if (!response.ok) {
      throw Error(response.statusText);
    }

    console.log(response.json());
  }
  catch (err) {
    console.error(err);
  }
})();

使用顶级的await,底部的代码变得更加吸引人,尽管在实践中,你通常是在编写一个函数。
我同意的一个例外是this answer提供的,即偶尔在await链上使用.catch来避免一种有点丑陋的try/catch
以下是一个可能有用的示例:
const fs = require("node:fs/promises");

const exists = async pathName =>
  !!(await fs.stat(pathName).catch(() => false));

可能更倾向于使用async/await/try/catch版本:
const exists = async pathName => {
  try {
    await fs.stat(pathName);
    return true;
  }
  catch (err) {
    return false;
  }
};

...或者也许不是,这取决于你是否认为catch版本太聪明了。

请注意,这里没有混合使用.thenawait,只是用.catch而不是try/except。这里的一般启发式原则是"越扁平越好"(.catchtry/catch更扁平,await.then更扁平)。

(是的,这个例子有点牵强,因为在这个特定任务中有一种相当简洁的使用.then/.catch的方法,但这种模式可能会偶尔出现在其他上下文中)

如果有任何疑问,请坚持"永不混合"的原则。


作为承诺,这是我见过的由于混合使用awaitthen(以及其他一些Promise反模式)而导致的混淆问题的一小部分示例:
- puppeteer - 错误:Protocol error (Network.getResponseBody):未找到给定标识符的资源 - 当用户从登录页面转到主页后,如何引用当前页面对象? - 为什么这个递归函数不能异步运行? - Playwright错误(Target closed)导航后 - 对于Promises和async-await感到困惑 - 设置puppeteer page.goto重试限制 - 如何在我的page.evaluate中使用page? - waitForSelector找不到相关部分 - Puppeteer:为什么会出现"Protocol error (Runtime.callFunctionOn):Target closed"错误? - 在puppeteer中难以确定何时关闭浏览器 以及相关主题:
- 等待Promise链有什么问题? - 能否在一个实现中混合使用await和then?

1
“我同意的异常” - 另请参阅我的答案 try-catch syntax in async/await。实际上,我也喜欢使用带有两个处理程序的 .then() 而不是 .catch() - 例如,在您的第一个片段中也可以使用它。 - Bergi
谢谢,这很有道理,但我认为绝大多数await/then混用都是有问题的,并且源于困惑。要知道它可以被明智地使用的少数边缘情况需要很多经验,因此我认为最好建议他们基本上永远不要混用(除非你真的知道自己在做什么)。 - ggorlen
哦,我完全同意!只是这个答案的一个主要部分是关于异常的,所以我想提到另一种情况。 - Bergi

2

这样做没有什么坏处,但会增加代码量并使代码难以阅读, 你可以尝试这样做:

async apiCall(params) {
 try{
   var results = await this.anotherCall()
    return results;
 } catch(err){
   //do something with the error
 }
}

虽然我不确定你在这里想做什么,但是只在try块中返回results意味着该函数可能会在错误情况下返回undefined。

此外,重要的是要记住异步函数始终返回一个Promise,因此使用apiCall函数有两种方式:

// 1- using .then to access the returned value
apiCall().then(results => {
   if(results){
      //doing something with the results
  }
})

// 2- awaiting the function inside another async function
async anotherFunction(){
  const results = await apiCall();
  if(results){
      //doing something with the results
  }
}

使用if(results)可以确保您处理的不是undefined。


“没有什么伤害,但是代码更多,更难读取”...“代码更多,更难读取”似乎是有害的。这往往会导致错误。为什么要在 OP 的代码中添加 if?这里大部分的代码都是不必要的。 - ggorlen

0
让我们首先专注于awaitthen之间的区别,以便理解将它们混合使用的合理性。 await会暂停异步语义执行链,直到完成参数异步调用。这意味着当前的异步函数不会完成其语义执行,而是允许处理消息队列中的其他运行异步调用和任务,同时进行awaitthen是一种让当前函数体完全结束执行并返回的方式,因此可以在异步调用图中启动其他调用,而无需等待结果,而是附加具有闭包的回调函数,该闭包包含完成线程上下文中执行逻辑所需的所有内容。
如果你需要在继续async链之前等待,并且需要在继续当前异步调用链之前完成then lambda,那么在异步函数上下文中放置awaitthen上语义上等同于将语句拆分为两个不需要闭包的等待。使用一次await用于异步调用,第二次用于then参数,而不是使用then使代码更易读,因为可以多行显示且减少了闭包。

因此,在这种情况下,语义上没有太多的理由将两者混合使用。

但是,如果你的函数完成了所有其他任务,并且在语义上返回之前没有等待完成的理由,那么使用await关键字和then/catch会在性能方面更好地解决异步图。

总结:这取决于情况。你的函数体中是否有紧随其后的await?如果有的话,可能每个语句都使用await更好,因为这样更易读。如果有一种方法可以封闭所需的任何内容并使用then链而不使用await,那么异步函数可能会在语义上立即返回以继续其他调用,并且最好根本不使用任何await。但同时使用两者几乎没有任何意义。

0
有技术上的危害吗?没有。但是,以那种特定的方式混合使用它们,有点不符合标准的语法,有人可能会说你的例子仅仅因为不一致的风格而有害。最好的做法是训练人们在正确的工具上进行标准化,就你的例子而言,我认为坚持使用await是正确的选择。
但是... "使用" promise.then 和 async/await 结合在一起有一个非常好且常见的用例;它只是与你的例子相反的流程。await 的整个意义在于等待和解包...一个 promise,对吗?那个 promise 必须来自于某个地方,对吗?也许是在模块边界的另一侧?在另一侧使用.then,然后用 await 消费它是一个很好的模式。你不是在 "在单个函数内部同时使用它们",那样会很丑陋。但是你是在 "在整个应用程序的范围内同时使用它们" ... 例如。
// MODULE-1
export async function apiCall(params) {
    return apiInstance.someRequest(params)
      .then(results => {
        //do any results transformations
        return processedResults;
      })
      .catch(error => {
        //handle any errors here
      });
  }

// MODULE-2
import { apiCall } from 'MODULE-1'

async function consumeApi() {
    ... do something
    const processedResults = await apiCall()
    .... do something else
}


-1
简单
async apiCall(params) {
var results = await this.anotherCall()
  .then(async (results) => {
    //do any results transformations
    await //your action
    return results;
  })
  .catch(error => {
    //handle any errors here
  });
return results;
}

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