2

现在,我的 try/catch 块是否能正常工作对我来说是个谜。我将它们设置在代码周围,然后,因为代码中的某些内容是“异步的”,这似乎是一种在操作系统级别分叉到另一个线程/进程的奇特方式,如果它发生在 try/catch 将被忽略代码。

我很好,我只是想知道是否有一些迹象?按照惯例,我理解,如果一个电话要求回调,它是异步的,否则它不是。我明白为什么回调意味着异步,但我担心反过来并不总是正确的:没有什么能阻止我用一个 try/catch 包围一个调用,该调用被加载到一个新的调用堆栈中并且也不要求回调。这对我来说似乎真的很混乱,如果可能的话,我希望对我的 try/catch 进行更多的控制,而不是使用默认回调来处理所有未捕获的异常。

  1. 是否有语义告诉我代码何时离开当前堆栈?

更新:这是一个例子:

var UserSchema = new mongoose.Schema({
  email: {type: String, unique: true},
  password: String,
  firstName: String,
  lastName: String,
  created_at: {type: Date, default: Date.now},
  updated_at: Date
});


var User = mongoose.model('User', UserSchema);

var users = [
  {email: 'foo@bar.com', firstName: 'Justin', lastName: 'Jones'},
  {email: 'foo@bar.com', firstName: 'Justin', lastName: 'Jones'}
];


users.forEach(function(user) {

          var newUser = new User(user);
          newUser.save(function(err, obj) {

            if (!err) console.log("Saved: ", obj.email);

          });

});

鉴于上面的代码,无法在 save() 中捕获异常,因为它发生在另一个调用堆栈中。当我调用 save() 时,有什么方法可以让我从外部知道这就是即将发生的事情吗?

更新:每个告诉我为此使用处理程序的人都应该阅读这个?显然建议不要处理未在其“线程执行”中捕获的异常,引号是因为它仅像线程一样起作用。

4

4 回答 4

4

“异步”不是“一种说分叉到另一个线程/进程的奇特方式”。

JavaScript 是单线程的。故事结局。语言级别没有分叉。

“异步”的意思就是它所说的:执行的顺序不是代码的顺序。当某个事件发生时,一些代码位 - 回调函数 - 将在某个时间点执行。它是一个基于事件的编程模型。

考虑这个简单的例子:

function hello () { alert("Hello!"); }

setTimeout(hello, 2000);

这是最基本形式的异步回调。您有一个回调处理程序 - 函数hello- 和一个事件生成器,在本例中是一个基于时间的事件生成器。

一旦事件发生(已经过去了 2 秒),就会调用回调处理程序。

现在进行修改:

function hello() { alert(foo.bar); }

try {    
  setTimeout(hello, 2000);
} catch (ex) {
  alert(ex.message);
}

我们在setTimeout. 它保护回调的注册,仅此而已。这一步很可能会成功,因此 try-catch 块永远不会做任何事情。回调本身将在 2 秒后失败这一事实自然不会影响 try-catch 块。这是您感到困惑的行为。

现在进行另一个修改:

function hello() { 
  try {    
    alert(foo.bar); 
  } catch (ex) {
    alert("foo.bar is not defined");
  }
}

setTimeout(hello, 2000);

现在try-catch 块保护了实际上可能失败的步骤。这意味着您必须在可能发生错误的地方使用 try-catch 块,而不是通常将它们包裹在程序的大部分区域(这似乎是您所做的)。

但是如何让异常做一些有用和可配置的事情呢?通过引入更多回调,自然而然。

function hello(onError) { 
  try {    
    alert(foo.bar); 
  } catch (ex) {
    onError("foo.bar is not defined", ex);
  }
}

function errorHandler(customMessage, exception) {
  alert(customMessage);
  // or do something with exception 
}

setTimeout(function () {
  hello(errorHandler)
}, 2000);

根据您添加的示例:

var saveUsers = function(users, onSuccess, onError) {
  users.forEach(function(user) {
    var newUser = new User(user);

    newUser.save(function(err, obj) {
      if (err) {
        onError(err, obj);
        return false;
      } else {
        onSuccess(obj);
      }
    });
  });
}

var users = [
  {email: 'foo@bar.com', firstName: 'Justin', lastName: 'J'},
  {email: 'foo@bar.com', firstName: 'Justin', lastName: 'J'}
];

saveUsers(
  users, 
  function (user) { console.log("Saved: ", user.email); },
  function (err, user) { console.log("Could not save: ", user.email); }
});
于 2012-04-24T19:47:07.637 回答
2

语义实际上只是当您调用的函数完成执行时。如果它产生一个 I/O 进程并注册一个事件,您的try-catch块将不会围绕它,因为它是通过隐式 Javascript 事件循环在另一个循环上执行的。

您正在执行的函数中是否存在回调参数与函数启动的工作是否会导致事件在其他地方触发无关。基于EventEmitter 的对象使用该机制注册处理程序.on('eventName', functionName),因此多个事件和多个处理程序可以访问相同的“工作”,但这一切都是由一个不接受回调的函数启动的。而Array对象的forEach方法接受回调并且是同步的。

简而言之,事件循环障碍之外的任何东西都不会导致抛出 Javascript 异常。只有 Javascript 方面的代码可以。因此,如果需要,您将try-catch块放在那一侧:如果该函数可能会引发错误,则在调用可能异步代码的函数中;如果调用可能会引发错误的回调函数本身,则将其放在回调函数本身中。如果它是异步的,那么从 Javascript 的角度来看,它们是两个独立的调用堆栈,因此它们具有不同的try-catch作用域;如果它是同步的,您将只需进行一组额外的try-catch检查,并且至少您会更好地了解可能引发错误的原因。

就个人而言,我认为 try-catch 不能在像 Javascript 这样的语言中工作,并且被添加以使其更像 Java,所以我尽量避免使用throw用于 Node.js 的代码。(例外情况是,如果它仅将它们用于无法工作的库/对象的初始配置,或者如果它使用它[在我看来,由于执行开销很差]作为打破深度同步代码的内部方式并且不会暴露给我。)

编辑:为了更好地解释 Javascript 中的调用堆栈,这是一个简单的 ASCII 图表,显示了堆栈的每一级与时间的关系:

== Javascript Event Loop =========================================================
== My Awesome Function ======================================    == My callback ==
    == Call to an async library ====================
        == I/O request and callback registration ===

抛出的任何东西都My callback将直接发送回 Javascript 事件循环,并且只能通过注册一个process.on('uncaughtException', myFunc)处理程序来处理它。基本上,您自己的任何代码都可以使用try-catch,但throw如果将其直接作为事件处理程序调用,则永远不应该。

至于您最近对问题的编辑async.forEach将解决您的问题。您将数组传递给它以进行迭代,然后将函数传递给数组中的每个项目,然后传递一个“finally”样式的函数来处理错误或从那里继续您的代码。

于 2012-04-24T19:45:42.377 回答
1

Nathan:我不打算讨论更大的问题,即找出“代码何时离开当前堆栈”。不过,我可以帮助您保存用户。我推荐伟大的异步库:

function saveUser(user, callback) {
    new User(user).save(callback);
}

async.forEach(users, saveUser, function(err, results) {
    // Respond
});
于 2012-04-24T20:15:25.790 回答
-1

如果您只是使用回调函数,那么没关系。NodeJS 是单线程的,因此一切都在同一个调用堆栈上。

但!你说得对,NodeJS 有时可能会“离开”当前的调用堆栈。离开是在引号中,因为它并没有真正离开当前调用堆栈,而只是返回到根调用 ( process.nextTick())。然后在下一个滴答声中,它会产生一个“新”调用堆栈。

如果你想要一个语义,我想你可以说在你使用的任何地方,你EventEmitter都会将该回调函数传递给下一个刻度,因此将它“移动”到另一个调用堆栈。(但即便如此,也不完全正确,因为 EventEmitter 实际上是在当前滴答声上调度事件)。

于 2012-04-24T20:05:47.487 回答