Heim  >  Artikel  >  Web-Frontend  >  Detaillierte Erläuterung der asynchronen Ausnahmebehandlung in js und der Verwendung des Domian-Tutorials

Detaillierte Erläuterung der asynchronen Ausnahmebehandlung in js und der Verwendung des Domian-Tutorials

零下一度
零下一度Original
2017-05-11 13:35:121382Durchsuche

In diesem Artikel werden hauptsächlich die asynchrone Ausnahmeverarbeitung und die Domänenmodulanalyse von Node.js vorgestellt, die einen bestimmten Referenzwert haben

Asynchrone Ausnahmebehandlung

Eigenschaften asynchroner Ausnahmen

Aufgrund der asynchronen Callback-Natur des Knotens ist es unmöglich, alle Ausnahmen durch try-catch-Ausnahmen abzufangen:

try {
 process.nextTick(function () {
  foo.bar();
 });
} catch (err) {
 //can not catch it
}

Für Webdienste ist es tatsächlich sehr wünschenswert, Folgendes zu haben:

//express风格的路由
app.get('/index', function (req, res) {
 try {
  //业务逻辑
 } catch (err) {
  logger.error(err);
  res.statusCode = 500;
  return res.json({success: false, message: '服务器异常'});
 }
});

Wenn Try Catch alle Ausnahmen abfangen kann, können wir einige Fehler im Code haben. Wenn ein Wenn ein unerwarteter Fehler auftritt, kann der Fehler aufgezeichnet und dem Anrufer freundlicherweise ein 500-Fehler zurückgegeben werden. Leider kann Try Catch in asynchronen Situationen keine Ausnahmen abfangen. Wir können also nur Folgendes tun:

app.get('/index', function (req, res) {
 // 业务逻辑 
});

process.on('uncaughtException', function (err) {
 logger.error(err);
});

Obwohl wir zu diesem Zeitpunkt das Fehlerprotokoll aufzeichnen können und der Prozess nicht normalerweise beendet wird, gibt es keine Möglichkeit, eine Anfrage zu stellen Wenn ein Fehler festgestellt wird, wird er auf freundliche Weise zurückgegeben. Er kann nur mit einer Zeitüberschreitung zurückgegeben werden.

Domäne

In Knoten v0.8+ wurde eine Moduldomäne veröffentlicht. Dieses Modul tut, was Try Catch nicht kann: Ausnahmen abfangen, die bei asynchronen Rückrufen auftreten.

Seitdem scheint unser hilfloses Beispiel oben eine Lösung zu haben:

var domain = require('domain');

//引入一个domain的中间件,将每一个请求都包裹在一个独立的domain中
//domain来处理异常
app.use(function (req,res, next) {
 var d = domain.create();
 //监听domain的错误事件
 d.on('error', function (err) {
  logger.error(err);
  res.statusCode = 500;
  res.json({sucess:false, messag: '服务器异常'});
  d.dispose();
 });
 
 d.add(req);
 d.add(res);
 d.run(next);
});

app.get('/index', function (req, res) {
 //处理业务
});

Wir führen Domänen in Form von Middleware ein, um asynchrone Ausnahmen zu behandeln. Obwohl die Domäne die Ausnahme erfasst hat, führt der durch die Ausnahme verursachte Stapelverlust natürlich immer noch zu Speicherverlusten. Wenn dies geschieht, müssen Sie den Prozess dennoch neu starten. Interessierte Schüler können sich die Domänen-Middleware ansehen . Stücke.

Seltsamer Fehler

Bei unserem Test verlief alles normal. Als es offiziell in der Produktionsumgebung verwendet wurde, stellten wir fest, dass die Domain plötzlich ausfiel! Die asynchrone Ausnahme wurde nicht abgefangen, was schließlich dazu führte, dass der Prozess abnormal beendet wurde. Nach einigen Untersuchungen wurde schließlich festgestellt, dass die Ursache auf die Einführung von Redis zum Speichern von Sitzungen zurückzuführen war.

var http = require('http');
var connect = require('connect');
var RedisStore = require('connect-redis')(connect);
var domainMiddleware = require('domain-middleware');

var server = http.createServer();
var app = connect();
app.use(connect.session({
 key: 'key',
 secret: 'secret',
 store: new RedisStore(6379, 'localhost')
}));
//domainMiddleware的使用可以看前面的链接
app.use(domainMiddleware({
 server: server,
 killTimeout: 30000
}));

Als zu diesem Zeitpunkt eine Ausnahme in unserem Geschäftslogikcode auftrat, stellten wir fest, dass diese nicht von der Domäne erfasst wurde! Nach einigen Versuchen wurde das Problem endlich lokalisiert:

var domain = require('domain');
var redis = require('redis');
var cache = redis.createClient(6379, 'localhost');

function error() {
 cache.get('a', function () {
  throw new Error('something wrong');
 });
}

function ok () {
 setTimeout(function () {
  throw new Error('something wrong');
 }, 100);
}
var d = domain.create();
d.on('error', function (err) {
 console.log(err);
});

d.run(ok);  //domain捕获到异常
d.run(error); //异常被抛出

Seltsam! Es handelt sich bei beiden um asynchrone Aufrufe. Warum wird ersterer erfasst, letzterer jedoch nicht?

Domänenanalyse

Rückblickend werfen wir einen Blick darauf, was die Domäne tut, damit wir asynchrone Anfragen erfassen können (der Code stammt aus Knoten v0.10.4, dieser Teil). Möglicherweise befindet es sich in einer schnellen Änderungsoptimierung.

KnotenereignisSchleifeMechanismus

Bevor wir uns das Prinzip der Domäne ansehen, müssen wir zunächst nextTick und _tickCalleZwei Methoden der Rückseite.

function laterCall() {
 console.log('print me later');
}

process.nextTick(laterCallback);
console.log('print me first');
Jeder, der node im obigen Code geschrieben hat, ist damit vertraut. Die Funktion von nextTick besteht darin, laterCallback zur Ausführung in die nächste Ereignisschleife einzufügen. Die _tickCallback-Methode ist eine nicht öffentliche Methode. Diese Methode dient zum Aufrufen der Eintragsfunktion

, um die nächste Ereignisschleife fortzusetzen, nachdem die aktuelle Zeitschleife endet. Mit anderen Worten: Der Knoten unterhält eine Warteschlange für die Ereignisschleife, nextTick wird in die Warteschlange gestellt und _tickCallback wird aus der Warteschlange entfernt.

Implementierung der Domäne

Nachdem wir den Ereignisschleifenmechanismus des Knotens verstanden haben, werfen wir einen Blick darauf, was die Domäne tut.

Domäne selbst ist eigentlich ein EventEmitter-Objekt, das erfasste Fehler durch Ereignisse liefert. Auf diese Weise vereinfachen wir es beim Studium auf zwei Punkte:

Wann wird das Fehlerereignis der Domäne ausgelöst:

Der Prozess hat eine Ausnahme ausgelöst, die von keinem abgefangen wurde Versuchen Sie es mit Catch. Wenn es sich im Domänenpaket befindet, wird das Ereignis „uncaughtException“ für den Prozess ausgelöst.


Wie die Domäne in mehreren verschiedenen Ereignisschleifen übergeben wird:

    Wenn eine Domäne instanziiert wird, rufen wir normalerweise ihre Ausführungsmethode auf (z. B. Früher im Web verwendet). Services), um eine Funktion im Paket dieses Domänenbeispiels auszuführen. Wenn die umschlossene Funktion ausgeführt wird, wird die globale
  1. -Variable

    von „process.domain“ auf diese Domäneninstanz verwiesen. Wenn in dieser Ereignisschleife eine Ausnahme auslöst, „processFatal“ aufruft und feststellt, dass „process.domain“ vorhanden ist, wird das Fehlerereignis in der Domäne ausgelöst.

  2. Nachdem require das Domänenmodul eingeführt hat, werden der globale nextTick und _tickCallback neu geschrieben und domänenbezogener Code eingefügt:
//简化后的domain传递部分代码
function nextDomainTick(callback) {
 nextTickQueue.push({callback: callback, domain: process.domain});
}

function _tickDomainCallback() {
 var tock = nextTickQueue.pop();
 //设置process.domain = tock.domain
 tock.domain && tock.domain.enter();
 callback();
 //清除process.domain
 tock.domain && tock.domain.exit();    
 }
};

这个是其在多个事件循环中传递domain的关键:nextTick入队的时候,记录下当前的domain,当这个被加入队列中的事件循环被_tickCallback启动执行的时候,将新的事件循环的process.domain置为之前记录的domain。这样,在被domain所包裹的代码中,不管如何调用process.nextTick, domain将会一直被传递下去。

当然,node的异步还有两种情况,一种是event形式。因此在EventEmitter的构造函数有如下代码:

 if (exports.usingDomains) {
  // if there is an active domain, then attach to it.
  domain = domain || require('domain');
  if (domain.active && !(this instanceof domain.Domain)) {
   this.domain = domain.active;
  }
 }

实例化EventEmitter的时候,将会把这个对象和当前的domain绑定,当通过emit触发这个对象上的事件时,像_tickCallback执行的时候一样,回调函数将会重新被当前的domain包裹住。

而另一种情况,是setTimeout和setInterval,同样的,在timer的源码中,我们也可以发现这样的一句代码:

 if (process.domain) timer.domain = process.domain;

跟EventEmmiter一样,之后这些timer的回调函数也将被当前的domain包裹住了。

node通过在nextTick, timer, event三个关键的地方插入domain的代码,让它们得以在不同的事件循环中传递。

更复杂的domain

有些情况下,我们可能会遇到需要更加复杂的domain使用。

domain嵌套:我们可能会外层有domain的情况下,内层还有其他的domain,使用情景可以在文档中找到

// create a top-level domain for the server
var serverDomain = domain.create();
serverDomain.run(function() {
 // server is created in the scope of serverDomain
 http.createServer(function(req, res) {
  // req and res are also created in the scope of serverDomain
  // however, we'd prefer to have a separate domain for each request.
  // create it first thing, and add req and res to it.
  var reqd = domain.create();
  reqd.add(req);
  reqd.add(res);
  reqd.on('error', function(er) {
   console.error('Error', er, req.url);
   try {
    res.writeHead(500);
    res.end('Error occurred, sorry.');
   } catch (er) {
    console.error('Error sending 500', er, req.url);
   }
  });
 }).listen(1337);
});

为了实现这个功能,其实domain还会偷偷的自己维持一个domain的stack,有兴趣的童鞋可以在这里看到。

回头解决疑惑

回过头来,我们再来看刚才遇到的问题:为什么两个看上去都是同样的异步调用,却有一个domain无法捕获到异常?理解了原理之后不难想到,肯定是调用了redis的那个异步调用在抛出错误的这个事件循环内,是不在domain的范围之内的。我们通过一段更加简短的代码来看看,到底在哪里出的问题。

var domain = require('domain');
var EventEmitter = require('events').EventEmitter;

var e = new EventEmitter();

var timer = setTimeout(function () {
 e.emit('data'); 
}, 10);

function next() {
 e.once('data', function () {
  throw new Error('something wrong here');
 });
}

var d = domain.create();
d.on('error', function () {
 console.log('cache by domain');
});

d.run(next);

此时我们同样发现,错误不会被domain捕捉到,原因很清晰了:timer和e两个关键的对象在初始化的时候都时没有在domain的范围之内,因此,当在next函数中监听的事件被触发,执行抛出异常的回调函数时,其实根本就没有处于domain的包裹中,当然就不会被domain捕获到异常了!

其实node针对这种情况,专门设计了一个API:domain.add。它可以将domain之外的timer和event对象,添加到当前domain中去。对于上面那个例子:

d.add(timer);
//or
d.add(e);

将timer或者e任意一个对象添加到domain上,就可以让错误被domain捕获了。

再来看最开始redis导致domain无法捕捉到异常的问题。我们是不是也有办法可以解决呢?

其实对于这种情况,还是没有办法实现最佳的解决方案的。现在对于非预期的异常产生的时候,我们只能够让当前请求超时,然后让这个进程停止服务,之后重新启动。graceful模块配合cluster就可以实现这个解决方案。

domain十分强大,但不是万能的。希望在看过这篇文章之后,大家能够正确的使用domian,避免踩坑。

【相关推荐】

1. 免费js在线视频教程

2. JavaScript中文参考手册

3. php.cn独孤九贱(3)-JavaScript视频教程

Das obige ist der detaillierte Inhalt vonDetaillierte Erläuterung der asynchronen Ausnahmebehandlung in js und der Verwendung des Domian-Tutorials. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn