我如何拒绝一个承诺,返回的异步/等待函数?

例如最初:

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.
  });
}

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


当前回答

这不是对@ tj的回答。克劳德。只是回复注释“实际上,如果异常将被转换为拒绝,我不确定如果它是一个错误,我是否真的会被打扰。我只抛出错误的理由可能并不适用。”

如果你的代码使用async/await,那么用一个Error而不是400来拒绝仍然是一个很好的做法:

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

其他回答

最好的方法是抛出一个Error包装值,这将导致一个被拒绝的承诺和一个Error包装值:

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

你也可以直接抛出值,但这样就没有堆栈跟踪信息了:

} catch (error) {
    throw 400;
}

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

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

(或者直接返回Promise.reject(400);,但同样没有上下文信息。)

在你的例子中,当你使用TypeScript并且foo的返回值是Promise<A>时,你会这样使用:

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`.
}

我知道这是一个老问题,但我只是偶然发现了这个线索,这里错误和拒绝之间似乎有一种冲突(至少在许多情况下),这与经常重复的不要使用异常处理来处理预期情况的建议相冲突。为了说明:如果一个异步方法试图验证一个用户,而认证失败,这是一个拒绝(两种预期的情况之一),而不是一个错误(例如,如果认证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

我知道在很多情况下,不值得花时间去寻找小的优化,但在大型系统中,这些事情可以产生巨大的累积差异,这是一个相当鲜明的对比。

所以…虽然我认为公认的答案的方法是声音的情况下,你期望在一个异步函数中处理不可预测的错误,在这种情况下,拒绝仅仅意味着“你将不得不与计划B(或C,或D…)”我认为我的偏好是拒绝使用自定义响应对象。

这不是对@ tj的回答。克劳德。只是回复注释“实际上,如果异常将被转换为拒绝,我不确定如果它是一个错误,我是否真的会被打扰。我只抛出错误的理由可能并不适用。”

如果你的代码使用async/await,那么用一个Error而不是400来拒绝仍然是一个很好的做法:

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

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

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

在ES7和异步函数中像这样使用它:

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
}

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

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

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