异步API是否应该同步抛出?

时间:2014-02-19 17:27:10

标签: javascript asynchronous promise api-design

我正在编写一个JavaScript函数,它发出HTTP请求并返回结果的承诺(但这个问题同样适用于基于回调的实现)。

如果我立即知道为该函数提供的参数是无效的,那么函数throw应该是同步的,还是应该返回被拒绝的promise(或者,如果您愿意,可以使用Error调用回调实例)?

异步函数总是以异步方式运行有多重要,特别是对于错误条件?如果你知道程序没有处于合适的状态以便进行异步操作,那么throw是否可以?

e.g:

function getUserById(userId, cb) {
  if (userId !== parseInt(userId)) {
    throw new Error('userId is not valid')
  }

  // make async call
}

// OR...

function getUserById(userId, cb) {
  if (userId !== parseInt(userId)) {
    return cb(new Error('userId is not valid'))
  }

  // make async call
}

5 个答案:

答案 0 :(得分:7)

最终同步投掷与否的决定取决于你,你可能会找到争论任何一方的人。重要的是记录行为并保持行为的一致性。

关于此事的意见是你的第二个选择 - 将错误传递给回调 - 似乎更优雅。否则,您最终得到的代码如下所示:

try {
    getUserById(7, function (response) {
       if (response.isSuccess) {
           //Success case
       } else {
           //Failure case
       }
    });
} catch (error) {
    //Other failure case
}

此处的控制流程略显混乱。

似乎在回调中使用单个if / else if / else结构并放弃周围的try / catch会更好。

答案 1 :(得分:2)

  

异步函数应始终以异步方式运行,尤其是错误条件,这有多重要?

Very important

  

如果您知道程序处于合适的状态以便异步操作继续进行,那么throw是否可以?

是的,我个人认为,如果这是与任何异步生成的错误完全不同的错误,并且无论如何都需要单独处理。

如果已知某些用户标识无效,因为它们不是数字,并且某些用户标识将在服务器上被拒绝(例如,因为它们已经被删除),您应该始终进行(异步!)回调对于这两种情况。如果异步错误仅来自网络问题等,您可能会以不同的方式发出信号。

当" 意外"时,您总是可以throw出现错误。如果您需要有效的用户ID,则可能会抛出无效的用户ID。如果你想预测无效的并期望呼叫者处理它们,你应该使用"统一"错误路由,它将是异步函数的回调/拒绝承诺。

并重复@Timothy:你应该始终记录行为并保持行为的一致性。

答案 2 :(得分:1)

回调API理想情况下不应抛出,但它们会抛出,因为它很难避免,因为你必须在任何地方尝试捕获。请记住,抛出函数不需要throw显式抛出错误。另外一点是,用户回调也很容易抛出,例如调用JSON.parse而不尝试catch。

所以这就是根据这些理想行为的代码:

readFile("file.json", function(err, val) {
    if (err) {
        console.error("unable to read file");
    }
    else {
        try {
            val = JSON.parse(val);
            console.log(val.success);
        }
        catch(e) {
            console.error("invalid json in file");
        }
    }
});

必须使用2种不同的错误处理机制真的很不方便,所以如果你不希望你的程序是一个脆弱的纸牌屋(通过不写任何尝试捕获)你应该使用统一所有异常处理的承诺单一机制:

readFile("file.json").then(JSON.parse).then(function(val) {
    console.log(val.success);
})
.catch(SyntaxError, function(e) {
    console.error("invalid json in file");
})
.catch(function(e){
    console.error("unable to read file")
})

答案 3 :(得分:1)

这在很大程度上是一个意见问题。无论您做什么,都要始终如一,并清楚地记录下来。

我可以向您提供的一条客观信息是,这是 JavaScript 的 async 函数设计中经常讨论的主题,您可能知道这些函数隐式地返回对它们的工作的承诺。您可能还知道第一个 asyncawait 之前的 return 函数部分是同步;它仅在await s 或返回时才变为异步。

TC39 最终决定,即使在 async 函数的同步部分中抛出的错误也应该拒绝其承诺而不是引发同步错误。例如:

async function someAsyncStuff() {
    return 21;
}

async function example() {
    console.log("synchronous part of function");
    throw new Error("failed");
    const x = await someAsyncStuff();
    return x * 2;
}
try {
    console.log("before call");
    example().catch(e => { console.log("asynchronous:", e.message); });
    console.log("after call");
} catch (e) {
    console.log("synchronous:", e.message);
}

您可以看到,即使 throw new Error("failed") 位于函数的同步部分,它也会拒绝承诺而不是引发同步错误。

即使对于在函数体中的第一条语句之前发生的事情也是如此,例如确定缺少的函数参数的默认值:

async function someAsyncStuff() {
    return 21;
}

async function example(p = blah()) {
    console.log("synchronous part of function");
    throw new Error("failed");
    const x = await Promise.resolve(42);
    return x;
}
try {
    console.log("before call");
    example().catch(e => { console.log("asynchronous:", e.message); });
    console.log("after call");
} catch (e) {
    console.log("synchronous:", e.message);
}

这失败了,因为它在运行代码以获取我未在调用中提供的 blah 参数的默认值时尝试调用不存在的 p。如您所见,即使这样也会拒绝承诺而不是抛出同步错误。

TC39 可以走另一条路,让同步部分引发同步错误,就像这个非 async 函数所做的那样:

async function someAsyncStuff() {
    return 21;
}

function example() {
    console.log("synchronous part of function");
    throw new Error("failed");
    return someAsyncStuff().then(x => x * 2);
}
try {
    console.log("before call");
    example().catch(e => { console.log("asynchronous:", e.message); });
    console.log("after call");
} catch (e) {
    console.log("synchronous:", e.message);
}

但他们经过讨论后决定采用一致的承诺拒绝。

因此,这是您在决定如何在自己的非async 函数中处理异步工作时需要考虑的具体信息。

答案 4 :(得分:0)

理想情况下,您将拥有一个多层架构,如控制器、服务等。如果您在服务中进行验证,请立即抛出并在您的控制器中有一个 catch 块来捕获错误格式并发送适当的 http 错误代码。通过这种方式,您可以集中所有不良请求处理逻辑。如果您处理每种情况,您最终会编写更多代码。但这正是我要做的。取决于您的用例