【问题标题】:How to reject in async/await syntax?如何拒绝异步/等待语法?
【发布时间】:2017-07-16 04:09:51
【问题描述】:

如何拒绝 async/await 函数返回的承诺?

例如原文:

foo(id: string): Promise<A> {
  return new Promise((resolve, reject) => {
    someAsyncPromise().then((value)=>resolve(200)).catch((err)=>reject(400))
  });
}

翻译成async/await

async foo(id: string): Promise<A> {
  try{
    await someAsyncPromise();
    return 200;
  } catch(error) {//here goes if someAsyncPromise() rejected}
    return 400; //this will result in a resolved promise.
  });
}

那么,在这种情况下,我该如何正确拒绝这个承诺呢?

【问题讨论】:

  • 避免使用Promise constructor antipattern!连第一个sn-p都应该写成foo(id: string): Promise&lt;A&gt; { return someAsyncPromise().then(()=&gt;{ return 200; }, ()=&gt;{ throw 400; }); }
  • 我认为将这个问题中的代码翻译成vanilla js是有帮助的,因为问题与类型签字无关。如果我这样做了,该编辑可能会被接受吗?
  • 我认为这些类型有助于使其更易于理解 - 你确切地知道它在每个实例中返回的内容。

标签: typescript asynchronous es6-promise ecmascript-2017


【解决方案1】:

最好的办法是用 throwError 包装值,这会导致被拒绝的 promise 用 Error 包装值:

} catch (error) {
    throw new Error(400);
}

你也可以只throw这个值,但是没有堆栈跟踪信息:

} catch (error) {
    throw 400;
}

或者,返回一个带有 Error 包装值的被拒绝的承诺,但这不是惯用的:

} catch (error) {
    return Promise.reject(new Error(400));
}

(或者只是return Promise.reject(400);,但同样没有上下文信息。)

在您的情况下,当您使用TypeScriptfoo 的返回值是Promise&lt;A&gt;,您将使用这个:

return Promise.reject<A>(400 /*or Error*/ );

async/await 的情况下,最后一个可能有点语义不匹配,但它确实有效。

如果你抛出一个Error,这与使用await 语法消耗你foo 的结果的任何东西都很好:

try {
    await foo();
} catch (error) {
    // Here, `error` would be an `Error` (with stack trace, etc.).
    // Whereas if you used `throw 400`, it would just be `400`.
}

【讨论】:

  • 并且由于 async/await 是关于将异步流带回同步语法,throwPromise.reject() IMO 更好。是否throw 400 是另一个问题。在 OP 中它拒绝 400,我们可以争辩说它应该拒绝 Error
  • 是的,但是,如果您的代码链真的使用 async/await,那么您将.....在这里很难输入,让我演示作为答案
  • 您是否有任何理由要抛出一个新错误,而不是在 catch 块中给您的错误?
  • @sebastian - 我不知道你的意思。在async 函数中,没有resolvereject 函数。有returnthrow,这是解决和拒绝async 函数承诺的惯用方式。
  • @Jan-PhilipGehrcke - 你可以,但我从不这样做。它正在创建一个实例,new 明确表示。另请注意,如果您有 Error 子类 (class MyError extends Error),则不能省略它,所以...
【解决方案2】:

可能还应该提到的是,您可以在调用异步操作之后简单地链接 catch() 函数,因为在后台仍然会返回一个 Promise。

await foo().catch(error => console.log(error));

如果您不喜欢 try/catch 语法,这样可以避免它。

【讨论】:

  • 所以如果我想拒绝我的async 函数,我会抛出异常,然后用.catch() 很好地捕获它,就像我返回Promise.reject 或调用reject 一样。我喜欢!
  • 我不明白为什么这应该是公认的答案。接受的答案不仅更清晰,而且还可以在一个例程中处理所有可能的await 故障。除非每个await 都需要非常具体的案例,否则我不明白您为什么要这样抓住它们。只是我的拙见。
  • @jablesauce 对于我的用例,我不仅需要分别捕获每个 await 失败,而且还需要使用基于 Promise 的框架,该框架拒绝错误时的承诺。跨度>
  • 我喜欢尽可能使用它,但是如果 catch 的预期行为是从外部范围(调用 foo() 的范围)返回,那么你不能使用这个解决方案。在这种情况下,我不得不使用 try-catch 块,因为 catch 的 lambda 函数内的 return 语句只会从 lambda 函数返回,而不是从外部作用域返回。
  • @AshishRawat 不正确。如果没有 await 关键字,则将挂起的 Promise 返回到等待变量。使用 await 关键字,它确保(如果 promise 正确解析)返回值是已解析的 promise 结果。
【解决方案3】:

您可以创建一个包装函数,它接受一个promise,如果没有错误则返回一个包含数据的数组,如果有错误则返回错误。

function safePromise(promise) {
  return promise.then(data => [ data ]).catch(error => [ null, error ]);
}

ES7async 函数中像这样使用它:

async function checkItem() {
  const [ item, error ] = await safePromise(getItem(id));
  if (error) { return null; } // handle error and return
  return item; // no error so safe to use item
}

【讨论】:

  • 看起来像是尝试拥有可爱的 Go 语法但没有太多优雅。我发现使用它的代码被混淆了,足以从解决方案中吸取价值。
【解决方案4】:

编写异步函数的更好方法是从一开始就返回一个待处理的 Promise,然后在 Promise 的回调中处理拒绝和解决方案,而不是仅仅在错误时吐出一个被拒绝的 Promise。示例:

async foo(id: string): Promise<A> {
    return new Promise(function(resolve, reject) {
        // execute some code here
        if (success) { // let's say this is a boolean value from line above
            return resolve(success);
        } else {
            return reject(error); // this can be anything, preferably an Error object to catch the stacktrace from this function
        }
    });
}

然后你只需在返回的 Promise 上链接方法:

async function bar () {
    try {
        var result = await foo("someID")
        // use the result here
    } catch (error) {
        // handle error here
    }
}

bar()

来源 - 本教程:

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise

【讨论】:

  • 专门问的关于使用 async/await 的问题。不使用承诺
  • 这个答案并不是确定的正确答案。这是对上面给出的其他答案的支持答案。我会把它作为评论写下来,但鉴于我有代码,答案字段是一个更好的地方。
  • 感谢您的澄清。展示如何制作异步函数绝对是有帮助的。更新第二个代码块以使用 await 将更加相关和有用。干杯
  • 我已编辑您的回复以进行更新。如果我错过了什么,请告诉我
【解决方案5】:

这不是@T.J. 的答案。克劳德的。只是回复评论的评论“实际上,如果异常将被转换为拒绝,我不确定如果它是一个错误我是否真的感到困扰。我只抛出错误的原因可能不适用。 "

如果您的代码使用async/await,那么使用Error 而不是400 拒绝仍然是一个好习惯:

try {
  await foo('a');
}
catch (e) {
  // you would still want `e` to be an `Error` instead of `400`
}

【讨论】:

    【解决方案6】:

    我建议以一种新颖的方法正确处理拒绝,而无需使用多个 try-catch 块。

    import to from './to';
    
    async foo(id: string): Promise<A> {
        let err, result;
        [err, result] = await to(someAsyncPromise()); // notice the to() here
        if (err) {
            return 400;
        }
        return 200;
    }
    

    to.ts 函数应该从哪里导入:

    export default function to(promise: Promise<any>): Promise<any> {
        return promise.then(data => {
            return [null, data];
        }).catch(err => [err]);
    }
    

    致谢 Dima Grossman 在以下link

    【讨论】:

    • 我几乎只使用这种结构(更简洁),并且有一个'to'模块已经存在了一段时间npmjs.com/package/await-to-js。不需要单独的声明,只需将 let 放在解构赋值的前面。如果只检查错误,也可以只做let [err]=
    【解决方案7】:

    我知道这是一个老问题,但我只是偶然发现了这个问题,似乎错误和拒绝之间存在混淆,这与经常重复的不使用的建议相冲突(至少在许多情况下)异常处理以处理预期的情况。举例说明:如果异步方法正在尝试对用户进行身份验证并且身份验证失败,则这是拒绝(两种预期情况之一)而不是错误(例如,如果身份验证 API 不可用。)

    为了确保我不只是扯皮,我使用以下代码对三种不同的方法进行了性能测试:

    const iterations = 100000;
    
    function getSwitch() {
      return Math.round(Math.random()) === 1;
    }
    
    function doSomething(value) {
      return 'something done to ' + value.toString();
    }
    
    let processWithThrow = function () {
      if (getSwitch()) {
        throw new Error('foo');
      }
    };
    
    let processWithReturn = function () {
      if (getSwitch()) {
        return new Error('bar');
      } else {
        return {}
      }
    };
    
    let processWithCustomObject = function () {
      if (getSwitch()) {
        return {type: 'rejection', message: 'quux'};
      } else {
        return {type: 'usable response', value: 'fnord'};
      }
    };
    
    function testTryCatch(limit) {
      for (let i = 0; i < limit; i++) {
        try {
          processWithThrow();
        } catch (e) {
          const dummyValue = doSomething(e);
        }
      }
    }
    
    function testReturnError(limit) {
      for (let i = 0; i < limit; i++) {
        const returnValue = processWithReturn();
        if (returnValue instanceof Error) {
          const dummyValue = doSomething(returnValue);
        }
      }
    }
    
    function testCustomObject(limit) {
      for (let i = 0; i < limit; i++) {
        const returnValue = processWithCustomObject();
        if (returnValue.type === 'rejection') {
          const dummyValue = doSomething(returnValue);
        }
      }
    }
    
    let start, end;
    start = new Date();
    testTryCatch(iterations);
    end = new Date();
    const interval_1 = end - start;
    start = new Date();
    testReturnError(iterations);
    end = new Date();
    const interval_2 = end - start;
    start = new Date();
    testCustomObject(iterations);
    end = new Date();
    const interval_3 = end - start;
    
    console.log(`with try/catch: ${interval_1}ms; with returned Error: ${interval_2}ms; with custom object: ${interval_3}ms`);
    

    由于我对 Javascript 解释器的不确定性(我只喜欢一次只进入一个兔子洞),所以包含了其中的一些内容;例如,我包含了doSomething 函数并将其返回分配给dummyValue,以确保条件块不会被优化。

    我的结果是:

    with try/catch: 507ms; with returned Error: 260ms; with custom object: 5ms
    

    我知道在很多情况下不值得费心去寻找小的优化,但在大规模系统中,这些东西可以产生很大的累积差异,这是一个非常明显的比较。

    所以……虽然我认为接受的答案的方法是合理的,因为您希望必须在异步函数中处理不可预测的错误,如果拒绝只是意味着“您将不得不使用 Plan B(或 C,或 D……)”我认为我的偏好是拒绝使用自定义响应对象。

    【讨论】:

    • 另外,请记住,如果对异步函数的调用位于封闭范围内的 try/catch 块内,则无需为处理异步函数中的意外错误而感到压力,因为 - 与 Promises 不同 -异步函数将它们抛出的错误冒泡到封闭范围内,在那里它们的处理方式与该范围内的本地错误一样。这是 async/await 的主要好处之一!
    • 微基准是魔鬼。仔细看看数字。您需要做 1000 倍的操作才能注意到此处的 1 毫秒差异。是的,添加 throw/catch 会取消优化函数。但是a)如果您正在等待异步操作,则可能需要几个数量级的时间才能在后台发生比0.0005 Ms更长的时间。 b) 你需要做 1000 倍才能在这里产生 1 毫秒的差异。
    猜你喜欢
    • 2019-05-17
    • 2019-03-06
    • 2019-02-21
    • 1970-01-01
    • 2018-10-26
    • 1970-01-01
    • 2018-10-11
    • 1970-01-01
    • 2022-01-04
    相关资源
    最近更新 更多