如何为Node.js编写异步函数

114

我尝试了解异步函数应该如何编写。在浏览了大量文档之后,仍然不清楚。

我该如何为Node编写异步函数?我应该如何正确实现错误事件处理?

换句话说,我应该如何理解以下函数?

var async_function = function(val, callback){
    process.nextTick(function(){
        callback(val);
    });
};

另外,我发现这个SO问题(“如何在node.js中创建非阻塞异步函数?”)很有意思。我觉得它还没有得到解答。


14
这就是为什么我在问。对我来说这些功能的区别并不明显。 - Kriem
@davin - 猜想我还没有完全理解异步模型。 - Kriem
@Kriem,我昨天回答了一个可能有帮助的问题:http://stackoverflow.com/questions/6883648/how-to-send-a-return-form-a-callback-function-to-the-main-function/ 这不是你问题的答案,但它与主题相关。尝试阅读那里的问题和答案,并尝试使用代码来理解正在发生的事情。 - davin
2
@Raynos,“异步函数”的定义是什么? - Anderson Green
我认为任何使用套接字(数据库、流等)的东西都会导致函数异步执行,但我可能排除了其他函数也会异步执行的情况。 - Purefan
显示剩余4条评论
6个回答

86

你似乎把异步IO和异步函数混淆了。Node.js使用异步非阻塞IO是因为非阻塞IO更好。理解它的最好方法是观看Ryan Dahl的一些视频。

如何编写Node的异步函数?

只需编写普通的函数,唯一的区别是它们不会立即执行,而是以回调函数的形式传递。

如何正确地实现错误事件处理?

通常,API会将一个err作为第一个参数给你的回调函数。例如:

database.query('something', function(err, result) {
  if (err) handle(err);
  doSomething(result);
});

这是一种常见的模式。

另一种常见的模式是on('error')。例如:

process.on('uncaughtException', function (err) {
  console.log('Caught exception: ' + err);
});

编辑:

var async_function = function(val, callback){
    process.nextTick(function(){
        callback(val);
    });
};

当调用上述函数时:

async_function(42, function(val) {
  console.log(val)
});
console.log(43);

将异步地在控制台打印42。特别是,process.nextTick会在当前事件循环调用栈为空时触发。当async_functionconsole.log(43)运行完毕后,该调用栈为空。因此,我们会先输出43,再输出42。

你可能应该阅读一些有关事件循环的内容。


我看过Dahl的视频,但我恐怕还没有掌握这个问题。:( - Kriem
1
@Kriem 请查看更新后的答案,并阅读有关事件循环的内容:http://en.wikipedia.org/wiki/Event_loop - Raynos
1
谢谢你的见解。现在我更清楚自己知识上的不足了。 :) 顺便说一下,你最后的例子对我很有帮助。 - Kriem
我认为你对异步IO的说法“更好”太笼统了。从这个意义上来说是的,但总体而言可能并非如此。 - Jake B
在你的第一个代码示例中,你检查了err参数,但之后没有返回。如果出现错误,代码将继续执行,并可能导致应用程序出现严重问题。 - Gabriel McAdams

9

仅仅通过回调函数传递并不足够。

例如,您需要使用 settimer 来使函数异步。

示例:非异步函数:

function a() {
  var a = 0;    
  for(i=0; i<10000000; i++) {
    a++;
  };
  b();
};

function b() {
  var a = 0;    
  for(i=0; i<10000000; i++) {
    a++;
  };    
  c();
};

function c() {
  for(i=0; i<10000000; i++) {
  };
  console.log("async finished!");
};

a();
console.log("This should be good");

如果你运行上面的例子,这应该是好的,但必须等到这些函数完成工作才行。
伪多线程(异步)函数:
function a() {
  setTimeout ( function() {
    var a = 0;  
    for(i=0; i<10000000; i++) {
      a++;
    };
    b();
  }, 0);
};

function b() {
  setTimeout ( function() {
    var a = 0;  
    for(i=0; i<10000000; i++) {
      a++;
    };  
    c();
  }, 0);
};

function c() {
  setTimeout ( function() {
    for(i=0; i<10000000; i++) {
    };
    console.log("async finished!");
  }, 0);
};

a();
console.log("This should be good");

这个将会是真正的异步操作。 在异步操作完成之前,这个应该已经被执行完毕了。

5

3

如果您知道一个函数会返回一个 Promise,我建议在 JavaScript 中使用新的 async/await 特性。它使语法看起来同步但实际上是异步的。当您在函数中添加 async 关键字时,它允许您在该作用域中 await Promise:

async function ace() {
  var r = await new Promise((resolve, reject) => {
    resolve(true)
  });

  console.log(r); // true
}

如果一个函数没有返回一个Promise,我建议你将其包装在一个新的Promise中,然后解析你想要的数据:
function ajax_call(url, method) {
  return new Promise((resolve, reject) => {
    fetch(url, { method })
    .then(resp => resp.json())
    .then(json => { resolve(json); })
  });
}

async function your_function() {
  var json = await ajax_call('www.api-example.com/some_data', 'GET');
  console.log(json); // { status: 200, data: ... }
}

总之:发挥Promises的威力。


这里需要记住的是,Promise 的主体仍然是同步执行的。 - ns15

2
尝试这个,它适用于node和浏览器。
isNode = (typeof exports !== 'undefined') &&
(typeof module !== 'undefined') &&
(typeof module.exports !== 'undefined') &&
(typeof navigator === 'undefined' || typeof navigator.appName === 'undefined') ? true : false,
asyncIt = (isNode ? function (func) {
  process.nextTick(function () {
    func();
  });
} : function (func) {
  setTimeout(func, 5);
});

18
4个踩却没有一条具有建设性的评论.. :\ - Omer
6
这就是在SO上的生活。 - Piece Digital
6
也许这段代码对你来说很容易理解,但回答者可能并不清楚。因此,在给出负评时,解释一下总是一个好习惯。请注意,本次翻译没有改变原文的含义,同时尽可能使表述通俗易懂。 - Omer

0

我在node.js中为这样的任务处理了太多小时。我主要是前端开发人员。

我认为这非常重要,因为所有的node方法都是异步处理回调的,将其转换为Promise更容易处理。

我只想展示一种可能的结果,更加简洁和易读。使用ECMA-6和async,你可以像这样编写它。

 async function getNameFiles (dirname) {
  return new Promise((resolve, reject) => {
    fs.readdir(dirname, (err, filenames) => {
      err !== (undefined || null) ? reject(err) : resolve(filenames)
    })
  })
}

(undefined || null) 是针对repl(读取事件打印循环)场景的,使用 undefined 也可以。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接