无法在异步 promise 执行函数内部抛出错误

15

我一直试图理解为什么以下代码无法捕获throw。如果从new Promise(async (resolve, ...部分中移除async关键字,则工作正常,因此这与Promise执行程序是异步函数有关。

(async function() {

  try {
    await fn();
  } catch(e) {
    console.log("CAUGHT fn error -->",e)
  }

})();

function fn() {

  return new Promise(async (resolve, reject) => {
    // ...
    throw new Error("<<fn error>>");
    // ...
  });

}

这里这里这里的回答都重申了“如果你在任何其他异步回调中,则必须使用reject”,但是他们所说的“异步”并不是指async函数,因此我认为他们的解释不适用于这里(如果适用,我也不明白为什么)。

如果我们使用reject而不是throw,上面的代码可以正常工作。我想从根本上理解为什么throw在这里无法工作。谢谢!


1
永远不要使用异步执行器函数。你为什么要这样做呢? - Bergi
@Bergi,我可能陷入了反模式陷阱。我看不到我该如何以不同的方式处理这种情况(很可能是因为我是 Promise 的新手)?--> http://jsbin.com/waqatagaqa/edit?js (请注意,thing 是一个库,我不能控制/没有创建它) - user993683
你想要将fn函数变成async,以便可以await新构造的Promise,然后进行其他操作。执行程序不应该是async的,且只需要做一件事:等待事件发生并调用resolve / reject。你可能需要提出一个带有完整代码的新问题,这样我才能提供适当的答案。 - Bergi
我觉得我可能误解了你的意思,所以我按照你的建议创建了一个单独的问题:https://dev59.com/FVgQ5IYBdhLWcg3wPxhM。感谢你的帮助! - user993683
3个回答

39

这是Promise构造函数反模式async/await版本!

永远不要async function作为Promise执行器函数(即使你可以让它工作)

[1: 通过调用resolvereject而不是使用returnthrow语句]

他们所说的"异步"并不是指async函数,所以我认为他们的解释在这里不适用

他们也可能会这么认为。一个简单的不能工作的例子是

new Promise(async function() {
    await delay(…);
    throw new Error(…);
})

相当于

new Promise(function() {
    return delay(…).then(function() {
        throw new Error(…);
    });
})

现在清楚了,在异步回调函数中明确使用了 throw 语句。

Promise 构造器 只能捕获 同步 异常,而 async function 永远不会抛出异常——它总是返回一个 promise(但该 promise 可能会被拒绝)。但是返回值会被忽略,因为该 promise 正在等待调用 resolve


2
未来的搜索者应该查看这个后续问题,以了解有关Promise反模式的更多信息。 - user993683
你甚至可以更简短:return delay(…).then(function() { throw new Error(…); }); - Ivan Perevezentsev
@IvanPerevezentsev 这不是我在第二个无法工作的示例中提供的代码吗? - Bergi

6

因为在Promise执行程序中与外部世界“通信”的唯一方法是使用resolvereject函数。你可以使用以下示例:

function fn() {
  return new Promise(async (resolve, reject) => {
    // there is no real reason to use an async executor here since there is nothing async happening
    try {
      throw new Error('<<fn error>>')
    } catch(error) {
      return reject(error);
    }
  });
}

例如,当您想要执行一些具有方便的异步函数但也需要回调的操作时,可以使用以下人为的示例。该示例通过使用基于回调的 fs.writeFile 函数,并结合异步的 fs.promises.readFile 函数来读取文件并将其复制。在实际开发中,您不应该混用像这样的 fs 函数,因为没有必要。但是一些类库,例如 stylus 和 pug 使用回调,我在这些场景下经常使用这种技巧。
const fs = require('fs');

function copyFile(infilePath, outfilePath) {
  return new Promise(async (resolve, reject) => {
    try {
      // the fs.promises library provides convenient async functions
      const data = await fs.promises.readFile(infilePath);
      // the fs library also provides methods that use callbacks
      // the following line doesn't need a return statement, because there is nothing to return the value to
      // but IMO it is useful to signal intent that the function has completed (especially in more complex functions)
      return fs.writeFile(outfilePath, data, (error) => {
        // note that if there is an error we call the reject function
        // so whether an error is thrown in the promise executor, or the callback the reject function will be called
        // so from the outside, copyFile appears to be a perfectly normal async function
        return (error) ? reject(error) : resolve();
      });
    } catch(error) {
      // this will only catch errors from the main body of the promise executor (ie. the fs.promises.readFile statement
      // it will not catch any errors from the callback to the fs.writeFile statement
      return reject(error);
      // the return statement is not necessary, but IMO communicates the intent that the function is completed
    }
  }
}

显然,所有人都说这是一种反模式,但当我想在做某些异步操作之前执行只能使用回调函数完成的操作时(不像我编造的示例一样复制文件),我会经常使用它。我不明白为什么人们认为它是反模式(使用异步 promise 执行程序),而我还没有看到一个例子能够让我相信它应该被接受为一般规则。


1
我也经常做这件事。最近,我尝试将我的await语句移动到function myfunc()...return new Promise...之间,然后完全删除try/catch,以便我可以更改我的Promise执行程序,不再使用async,但是这样做太笨重了。虽然,我现在真的很好奇你的这个例子(以及我在项目中到处都在做的事情)是否真的会被算作这种反模式的一部分,如果是,为什么,以及(真正的)更好的替代方案是什么。 - SeriousLee
1
我几乎一直在使用它。为什么它被称为反模式,这将是很好的。 - vijayakumarpsg587

0
简要说明: 当您需要使用reject和resolve方法公开一些异步操作的状态时,您可以使用async函数本身来公开该操作的状态。
例如,这里我们正在使用Supabase SDK客户端编写一个签名函数,以在我们应用程序的多个部分中使用。
const signIn = async (
  email: string,
  password: string,
) => {
  const response =
    await supabaseClient.auth.signInWithPassword({
      email: email,
      password: password,
    });

 // save response.data.session.access_token in localstorage

  return response;
};

现在我们有一个异步函数叫做signIn,我们可以在其他地方使用它,获取所有的promise状态。
  try {
  const { data, error } = await signIn(email, password);
  if (error) {// Supabase internal error
    return alert(error.message);
  }
   // successful
   alert('successfully logged in');
  
} catch (err) { // like promise rejected 
  alert('Login Failed! Please try again.');
}

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