2016-08-09 283 views
6

Es ist üblich, Argumente zu validieren und Fehler in Funktionen zurückzugeben.JavaScript Callback Fehlerbehandlung

jedoch in JavaScript Callback-Funktion, wie zum Beispiel:

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

schrieb ich eine Menge von Funktionen wie diese, aber ich frage mich, ob es etwas potenziell schädlich. Da der Aufrufer in den meisten Fällen davon ausgeht, dass es sich um eine asynchrone Funktion handelt, wird der Rückruf nach dem Code direkt nach dem Funktionsaufruf ausgeführt. Wenn jedoch einige Argumente ungültig sind, ruft die Funktion sofort den Callback auf. Der Aufrufer muss also vorsichtig mit der Situation umgehen, dh mit einer unerwarteten Ausführungssequenz.

Ich möchte einige Hinweise zu diesem Thema hören. Sollte ich vorsichtig annehmen, dass alle asynchronen Rückrufe sofort ausgeführt werden können? Oder ich sollte etwas wie setTimeout (..., 0) verwenden, um synchrone Dinge in asynchrone umzuwandeln. Oder es gibt eine bessere Lösung, die ich nicht kenne. Vielen Dank.

Antwort

3

Eine API sollte dokumentieren, dass es den Rückruf nennen entweder synchron (wie Array#sort) oder asynchron (wie Promise#then) und dann immer diese dokumentiert Garantie gehorchen. Es sollte nicht zusammenpassen.

Also, wenn Sie eine Funktion haben, die normalerweise einen Rückruf asynchron aufruft, sollte es immer asynchron anrufen, unabhängig davon, warum es den Anruf macht.

Es war ein großartiges Beispiel in jQuery: Wenn jQuery ersten „deferred“ Objekte hinzugefügt, sie würden den Rückruf nennen synchron, wenn die latent hatte bereits abgerechnet worden, aber es hatte asynchron, wenn nicht. Dies war die Ursache für viele Verwirrungen und Bugs, weshalb ES2015 verspricht, dass then und catch Callbacks immer asynchron aufgerufen werden.


Wenn möglich und nicht im Widerspruch mit dem Rest der Codebasis, schaut Promises anstatt einfach Rückrufe bei Verwendung. Versprechen bieten eine sehr klare, einfache, garantierte Semantik und Zusammensetzbarkeit für asynchrone Operationen (und Interoperabilität mit synchronen).

+0

Ich habe meinen Downvote entfernt. Ich schätze, dass die Validierung der Art, wie die Funktion aufgerufen wird, schwerwiegend genug ist, um geworfen zu werden und in der Produktion sowieso nicht passieren sollte. –

+0

@PatrickRoberts: Ich habe diesen Teil der Antwort entfernt, es war nicht wirklich relevant für die Frage. Ich kann Ihren Standpunkt sehen, obwohl ich (noch) nicht damit einverstanden bin; Ich muss eher darüber nachdenken. Ich bemerke, dass, wenn Sie während des Setups eines ES2015-Versprechens werfen ('let p = neues Versprechen (resolve => {new error();})'), wird es in eine Ablehnung durch den Promise-Konstruktor konvertiert, der Ihre unterstützt Argument für einen einzigen Fehler Kanal angesichts der Gedanken und Erfahrungen, die in das Design dieser API ging ... –

+0

Mein Ansatz ist auch nicht korrekt. Wie der Kommentator auf mich hingewiesen hat, ist das Callstack etwas sehr Wichtiges zu beachten, besonders wenn ein Entwickler, der Ihre Funktion verwendet, versucht, unbegrenzt zu wiederholen, was letztendlich zu einem Stackoverflow führt, wenn Ihre Funktion synchron ist, wenn sie fehlerhaft ist. –

-1

Nun, da der Aufrufer erwartet, dass die Funktion asynchron ist, die Callback-Funktion sofort oder in wenigen Sekunden auszuführen, macht keinen Unterschied.

Sie müssen keine return tun, weil Sie die Rückruffunktion haben.

+0

Der 'return' ist vorhanden, um die Funktion vorzeitig zu beenden und keinen Wert zu liefern. –

+0

Sorry, habe den Kommentar dort nicht gesehen. –

0

Nein, sofort zurückrufen ist nicht schädlich, und absichtlich in der Tat um den Fehler zu verzögern verschwendet nur Zeit und Aufwand. Ja, ein sofortiger Aufruf eines Fehlers kann sehr schädlich sein und sollte für eine Funktion, die als asynchron angenommen wird, vermieden werden! (Schaut euch das, eine 180!)

Aus Sicht der Entwickler gibt es mehrere gute Gründe, warum gründen kann erst nach erfolgen. Zum Beispiel here:

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

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

Das listening Ereignis angebracht ist erst nach .listen(8080) aufgerufen wird, weil die Ereignisquelle aus dem Aufruf von .listen() zurückgeführt wird.In diesem Fall wird die Implementierung des listening-Ereignisses zum synchronen Aufruf nach .listen() nicht erfolgreich ausgeführt.

Hier ist ein weiterer Fall Ich mag würde präsentieren:

var num = '5'; 

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

    // handle result 
}); 

Nun, wenn Sie callback mit dem Fehler synchron dieser Steuerungsablauf in einem Stackoverflow führen. Während dies der Fehler des Entwicklers ist, ist ein Stapelüberlauf eine wirklich schlechte Sache, die von einer Funktion auftritt, von der erwartet wird, dass sie asynchron ist. Dies ist ein Vorteil der Verwendung von setImmediate(), um den Fehler zu übergeben, anstatt die callback sofort auszuführen.

0

Der Aufrufer Ihrer asynchronen Funktion sollte wissen, was das Ergebnis des Aufrufs der Funktion sein wird. Es gibt einen Standard für das, was eine asynchrone Funktion zurückgeben sollte, Promises.

Wenn Ihre Funktion eine Promise zurückgibt, kann jeder leicht verstehen, was in dieser Funktion vor sich geht. Versprechen haben den Ablehnungsrückruf, aber wir könnten argumentieren, ob die Validierung der Parameter durch Verwerfen des Versprechens behandelt werden soll oder ob eine Ausnahme direkt ausgelöst werden soll. In jedem Fall werden, wenn der Aufrufer Ausnahmen ordnungsgemäß behandelt, die Methode catch verwendet, werden sowohl die direkt ausgelösten Ausnahmen als auch die Zurückweisungen auf die gleiche Weise erfasst.

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

Beide Muster führen dazu, dass der Fehler erfasst und protokolliert wird.

Wenn Sie Versprechungen verwenden, muss sich der Aufrufer Ihrer asynchronen Funktion nicht um Ihre Implementierung innerhalb der Funktion kümmern und Sie können den Fehler entweder direkt nach vorne werfen oder das Versprechen nach Belieben ablehnen.