2016-08-09 165 views
6

验证参数并返回函数中的错误很常见。JavaScript回调错误处理

然而,在JavaScript回调函数,如:

function myFunction(num, callback) { 
    if (typeof num !== 'number') return callback(new Error('invalid num')) 
    // do something else asynchronously and callback(null, result) 
} 

我写了很多这样的功能,但我不知道是否有什么东西可能有害。因为在大多数情况下,调用者认为这是一个异步函数,并且回调将在函数调用后的代码之后执行。但如果某些参数无效,该函数将立即调用回调函数。所以调用者必须小心处理这种情况,即意外的执行顺序。

我想听听关于这个问题的一些建议。我是否应该认真考虑所有异步回调可能会立即执行?或者我应该使用类似setTimeout(...,0)的东西来将同步事物转换为异步事件。或者有更好的解决方案,我不知道。谢谢。

回答

3

的API应当记录,它会调用回调要么同步(如Array#sort)或异步(如Promise#then),然后始终服从,所记录的保证。它不应该混合搭配。

所以是的,如果你有一个通常会异步调用回调函数,它应该总是异步调用它,而不管它为什么进行调用。在jQuery中有一个很好的例子:当jQuery第一次添加“延迟”对象时,如果延期已经结算,他们会同步调用回调,但是如果没有,则是异步调用。这是很多混淆和错误的根源,这也是ES2015承诺保证thencatch回调总是被异步调用的原因之一。


如果可能的话,而不是在与代码库的休息机会,看看使用Promises而不是简单的回调。 Promise为异步操作(以及与同步操作的互操作)提供了非常清晰,简单,有保证的语义和可组合性。

+0

我删除了我的downvote。我猜测函数被调用的方式严重到足以引发生产,而且不应该在生产中发生。 –

+0

@PatrickRoberts:我已经删除了答案的这一部分,这与问题并不相关。我可以看到你的观点,尽管我还没有同意它。我需要更多地考虑它。我注意到如果你在设置一个ES2015 Promise('let p = new Promise(resolve => {throw new Error();})')的时候抛出它,它会被Promise构造器转换为拒绝,赞成单一错误渠道的观点,因为这种API的设计思想和经验...... –

+0

我的方法也是不正确的。正如评论者指出的那样,callstack是非常重要的考虑事项,特别是当开发人员使用你的函数试图无限期地重试时,如果你的函数在出现错误时是同步的,最终会导致一个stackoverflow。 –

-1

那么,由于调用者期望函数是异步执行回调函数立即或在几秒钟内没有什么区别。

因为您有回调函数,所以您不需要执行return

+0

'返回'是为了提前退出函数,而不是提供一个值。 –

+0

对不起,没有看到那里的评论。 –

0

不,立即回拨并不是有害的,事实上有意拖延错误只会浪费时间和开销。是的,立即回拨错误可能是非常有害的,对于被假定为异步的函数应该是避免的! (看,180!)

从开发人员的角度来看,为什么只能在以后完成设置有很多很好的理由。例如here

const server = net.createServer(() => {}).listen(8080); 

server.on('listening',() => {}); 

listening事件没有附着直到.listen(8080)被调用之后,因为该事件源从调用返回到.listen()。在这种情况下,执行事件以在执行.listen()后同步调用将不成功。

这里想提出我另一种情况:

var num = '5'; 

myFunction(num, function callback(err, result) { 
    if (err) { 
    return myFunction(num, callback); 
    } 

    // handle result 
}); 

现在,如果你callback与同步的错误,这个控制流程将导致计算器。虽然这是开发人员的错,但从预计会异步的函数中发生的stackoverflow是一件非常糟糕的事情。这是使用setImmediate()传递错误而不是立即执行callback的一个优点。

0

异步函数的调用者应该知道调用函数的结果是什么。对于什么是异步函数应该返回一个标准Promise。

如果您的函数返回Promise,任何人都可以轻松理解该函数中发生了什么。承诺具有拒绝回调,但我们可以争论是否应通过拒绝承诺来处理参数的验证,或者是否应该直接引发异常。无论哪种方式,如果调用者使用catch方法正确处理异常,则直接抛出的异常和拒绝将以相同的方式捕获。

function throwingFunction(num) { 
    return new Promise(function (resolve, reject) { 

    if (typeof num !== 'number') throw new Error('invalid num'); 
    // do something else asynchronously and callback(null, result) 
    }; 
} 

function rejectingFunction(num) { 
    return new Promise(function (resolve, reject) { 

    if (typeof num !== 'number') reject(new Error('invalid num')); 
    // do something else asynchronously and callback(null, result) 
    }; 
} 

// Instead of passing the callback, create the promise and provide your callback to the `then` method. 

var resultThrowing = throwingFunction(num) 
    .then(function (result) { console.log(result); }) 
    .catch(function (error) { console.log(error); }); 

var resultRejecting = rejectingFunction(num) 
    .then(function (result) { console.log(result); }) 
    .catch(function (error) { console.log(error); }); 

这两种模式都会导致错误被捕获和记录。

如果使用promise,异步函数的调用者将不必担心函数内部的实现,并且您可以随时抛出错误或拒绝承诺。