在Node.js中顺序运行代码

3

我有一个从数据库中获取数据的函数:

recentItems = function () {
  Items.find({item_published: true}).exec(function(err,item){
      if(!err)
        return item
  });
};

我希望能像这样使用它:

var x = recentItems();

但是由于recentItems的异步特性,这种方法会返回未定义的值。我知道我可以改变我的函数并使用回调,像这样:

recentItems = function (callback) {
  Items.find({item_published: true}).exec(function(err,item){
      if(!err)
        callback(item)
  });
};

并且:

recentItems(function(result){
  var x = result;
});

但我不想使用这种方法,因为我的情况是这样的。我有一个函数应该执行两个操作,将结果推送到一个数组中,并在它们之后触发回调并返回值:

var calc = function(callback){
   var arr = [];

   var b = getValues();
   arr.push(b);

   recentItems(function(result){
     var x = result;
     arr.push(x);
   });

   callback(arr);
};

在这种情况下,将b的值推送到arr中并调用主回调函数,然后由于recentItems的异步行为,获取x的值。但我需要这两个操作依次运行且一个接一个地运行。在计算完所有内容之后,最后一行再运行并调用回调函数。
如何解决这个问题?我了解了PromisesAsync库,但我不知道哪一个是我的答案。我能用原始的Node.js来解决吗?如果可以的话,我宁愿使用它。

1
这可能是关于node.js最流行的重复问题之一。你的操作是异步的。你不能从同步函数返回结果,因为异步结果会在未来的某个时候发生。如果你想链接多个操作,则可以嵌套你的回调或使用promises或使用async库。 - jfriend00
1
@Fcoder:如果你是异步代码的新手,那么克服它的正确方法就是不要去克服它。你已经知道如何处理它了,所以只需这样做即可。一旦你有了更多的经验,可以看一下 Promise。请注意,Promise 并不消除回调的需要,它们只是重构你的回调以消除嵌套。因此,在学习 Promise 之前,请先熟悉常规回调。 - slebetman
2
我不同意 duplicate 标志,因为NodeJS 4.0.0非常新,今天有新的方法可以实现OP想要的功能。 - Buzinas
1
一堆关于异步操作顺序的问题和示例:https://dev59.com/DFwY5IYBdhLWcg3w9rso#32028826,https://dev59.com/hl0a5IYBdhLWcg3w07zt#29906506,http://stackoverflow.com/questions/29454785/async-js-and-series-issue/29454856#29454856,https://dev59.com/hl0a5IYBdhLWcg3w07zt#29906506。 - jfriend00
1
@Buzinas - 就此而言,node.js 4.0并没有添加任何新内容。所有潜在的解决方案,如promises或async库,在0.12中也都有了。此外,还没有人将其标记为任何重复内容。 - jfriend00
显示剩余8条评论
3个回答

3

有一些方法可以实现你想要的功能,但目前还没有完美的解决方案。

有一个ES7提案的本地async/await,将成为“回调天堂”,但目前你可以使用以下方法:

  • 嵌套回调(原生,但代码非常丑陋且难以维护)
  • Promises(很好,但仍然太冗长)
  • Async/Await库(这是一个很棒的库,但与原生相比还差得很远,而且性能不够好)
  • ES7转译器 - 你可以今天就编写ES7代码,并将其转译为ES5(例如 Babel

但是,如果您已经使用了最新版本的NodeJS(写作时为4.0.0),而且如果您没有使用,那么您真的应该使用生成器来实现您想要的功能。

结合一个名为co的小型库,它将帮助您几乎实现ES7 async/await所提出的内容,并且它主要使用本地代码,因此可读性和性能都非常好:

var co = require('co');

var calc = co(function *calc() {
  var arr = [];
  var b = getValues();
  arr.push(b);

  var items = yield recentItems();
  arr.push(items);

  return arr;
});

function recentItems() {
  return new Promise(function(resolve) {
    Items.find({item_published: true}).exec(function(err, item) {
      if(!err)
        resolve(item);
  });
}

你可以阅读这篇令人惊叹的Thomas Hunter博客文章了解更多关于此主题的内容

你可能需要提到这是ES6,可以通过在node 0.11及更高版本中传递--harmony标志来获取它。另外,我建议为了可读性不要将所有内容都包装在回调函数中。可以像这样:var run = require('co');function *main () { } run(main) 看起来更加熟悉。 - chriskelly
如果发生错误,不要忘记“拒绝(reject)”承诺!此外,您可能应该从承诺库中使用“promisification”函数。或者只需使用“exec()”返回的承诺。 - Bergi

1

你已经接近成功了。没有绕过回调的方法。但是,你可以使用回调来实现你想要的功能。只需嵌套它们即可:

var calc = function(callback){
   var arr = [];

   getValues(function(b){
       arr.push(b);

       recentItems(function(result){
         var x = result;
         arr.push(x);

         callback(arr);
       });
   });
};

1
“getValues” 在 OP 的代码中似乎是同步的,不是吗? - Bergi

1
您可以尝试像这样的方式。虽然它仍然嵌套了回调函数,但代码更加简洁。
var callA = function(callback) {
  //Run the first call
  prompt(callback(data));
}


var callB = function(callback) {
  //Some other call
  prompt(callback(data));
}

callA(function(dataA) {
  callB(function(dataB) {
    //Place a handler function here
    console.log(dataA + " " + dataB)
  })
});

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