wake-up-neo.com

JavaScript Callback-Fehlerbehandlung

Es ist üblich, Argumente zu überprüfen und Fehler in Funktionen zurückzugeben.

In der JavaScript-Rückruffunktion können Sie jedoch Folgendes tun:

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

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

Ich möchte einige Ratschläge zu diesem Thema hören. Soll ich vorsichtig davon ausgehen, dass alle asynchronen Callbacks sofort ausgeführt werden können? Oder ich sollte etwas wie setTimeout (..., 0) verwenden, um synchrones Ding in asynchrones umzuwandeln. Oder es gibt eine bessere Lösung, die ich nicht kenne. Vielen Dank.

8
matianfu

Eine API sollte dokumentieren, dass sie den Rückruf entweder synchron (wie Array#sort) oder asynchron (wie Promise#then) aufruft, und dann diese dokumentierte Garantie immer befolgen. Es sollte nicht zusammenpassen.

Wenn Sie also über eine Funktion verfügen, die normalerweise einen Rückruf asynchron aufruft, sollte immer es asynchron aufrufen, unabhängig davon, warum der Aufruf erfolgt.

In jQuery gab es ein hervorragendes Beispiel: Wenn jQuery zuerst "zurückgestellte" Objekte hinzufügte, würden sie den Rückruf synchron aufrufen, wenn der zurückgestellte bereits erledigt war, aber asynchron, wenn dies nicht der Fall war. Dies war die Quelle vieler Verwirrungen und Fehler. Dies ist auch ein Grund dafür, warum die Versprechen von ES2015 garantieren, dass then und catch-Callbacks immer asynchron aufgerufen werden.


Wenn möglich und nicht mit dem Rest der Codebase unvereinbar, verwenden Sie Promises und nicht einfache Rückrufe. Versprechungen bieten eine sehr klare, einfache, garantierte Semantik und Zusammensetzbarkeit für asynchrone Operationen (und die Zusammenarbeit mit synchronen Operationen).

4
T.J. Crowder

Nein, ein sofortiger Rückruf ist nicht schädlich. Tatsächlich verschwendet eine gezielte Verzögerung des Fehlers nur Zeit und zusätzlichen Aufwand. Ja, ein sofortiger Rückruf für einen Fehler kann sehr schädlich sein und sollte für eine Funktion, die als asynchron angenommen wird, vermieden werden! (Sieh dir das an, eine 180!)

Aus der Sicht eines Entwicklers gibt es mehrere gute Gründe, warum die Einrichtung nur nachher erfolgen kann. Zum Beispiel hier :

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

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

Das listening-Ereignis wird erst nach dem Aufruf von .listen(8080) angehängt, da die Ereignisquelle vom Aufruf an .listen() zurückgegeben wird. In diesem Fall ist das Implementieren des listening-Ereignisses, das nach Ausführung von .listen() synchron aufgerufen wird, nicht erfolgreich.

Hier ist ein weiterer Fall, den ich vorstellen möchte:

var num = '5';

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

  // handle result
});

Wenn Sie nun callback mit dem Fehler synchron arbeiten, führt dieser Kontrollfluss zu einem Stackoverflow. Obwohl dies der Fehler des Entwicklers ist, ist ein Stackoverflow eine wirklich schlechte Sache, wenn eine Funktion auftritt, die asynchron ist. Dies ist ein Vorteil der Verwendung von setImmediate(), um den Fehler zu übergeben, anstatt die callback sofort auszuführen.

1
Patrick Roberts

Der Aufrufer Ihrer asynchronen Funktion sollte wissen, was aus dem Aufruf der Funktion resultiert. Es gibt einen Standard dafür, was eine asynchrone Funktion zurückgeben soll, Promises.

Wenn Ihre Funktion eine Promise zurückgibt, kann jeder leicht verstehen, was in dieser Funktion passiert. Versprechungen haben den Rückweisungsrückruf, aber wir könnten argumentieren, ob die Validierung der Parameter durch Ablehnen des Versprechens gehandhabt werden sollte oder ob eine Ausnahme direkt ausgelöst werden sollte. Wenn der Aufrufer Ausnahmen ordnungsgemäß mit der catch-Methode behandelt, werden beide Ausnahmen und Ablehnungen 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 abgefangen 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.

1
Marco Scabbiolo