循环和回调地狱

时间:2018-01-17 13:38:30

标签: javascript node.js

假设您有一个包含值列表的数组/对象。让我们说一下mysql命令或url或filespaths。现在,您想迭代所有这些并在每个条目上执行一些代码。

for(let i = 0; i < urls.length; i++){
   doSthWith(urls[i]);
}

到目前为止没有问题。但是现在让我们说每个函数都有一个回调并且需要上次执行的结果。例如您从一个网站请求某些内容,并且您希望将此请求的结果用于以下某个请求。

for(let i = 0; i < urls.length; i++){
    if(resultOfLastIteration.successful){  //or some other result besides the last one
        doSthWith(urls[i]);
    }
}

现在让我们说网址的长度(或类似的)超过100.这就是为什么你正常使用一个循环所以你不需要写100次相同的功能。这也意味着Promises也不会做这个伎俩(除了我不知道欺骗技巧),因为你有同样的问题:

doSthWith(urls[0]).then(...
    doSthWith(urls[1]).then(... //either put them inside each other
).then(...
    doSthWith(urls[i])          //or in sequence 
    ...
).catch(err){...}

无论哪种方式,我都没有看到使用循环的方法。

我找到的一种方式,但并不是真的&#34;好&#34;是使用包&#34; wait.for&#34;(https://www.npmjs.com/package/wait.for)。但是,这个包很棘手的是每次你想使用wait.for时发射光纤:

 //somewhere you use the function in a fiber Context
 wait.for(loopedExecutionOfUrls, urls); 
 //function declaration
 function loopedExecutionOfUrls(urls, cb){
     //variables: 
      for(let i = 0; i < urls.length; i++){
          if(someTempResultVar[i-1] === true){ 
            someTempResultVar = wait.for(doSthWith,urls[i]);
          } else if(...){...}
      }
 }

但是我不确定这种方法是否真的很好,除了你总是要检查你是否已经将整个事物包装在一个光纤中,因此对于每个具有带回调功能的循环的函数。因此,您有3个级别:lauchFiber级别,wait.for(loopedFunction)级别和wait.for回调函数级别。 (希望我认为这是可以理解的)

所以我的问题是:你们有一个很好的方法,你可以循环抛出回调函数,并可以随时使用那些结果吗?

好=易于使用,阅读,高效,不递归,......

(对不起,如果这个问题很愚蠢,但我真的遇到了这个异步编程的问题)

3 个答案:

答案 0 :(得分:3)

如果你想在做同样的事情之前等待doSthWith但是使用nex网址,你必须链接你的承诺,你可以使用array.prototype.reduce来做到这一点:

&#13;
&#13;
urls = ["aaa", "bbb", "ccc", "ddd"];

urls.reduce((lastPromise, url) => lastPromise.then((resultOfPreviousPromise) => { 
    console.log("Result of previous request: ", resultOfPreviousPromise); // <-- Result of the previous request that you can use for the next request
    return doSthWith(url);
}), Promise.resolve());

function doSthWith(arg) {   // Simulate the doSthWith promise
   console.log("do something with: ", arg);
   return new Promise(resolve => {
      setTimeout(() => resolve("result of " + arg), 2000);
   });       
}
&#13;
&#13;
&#13;

答案 1 :(得分:2)

使用async,特别是async.each

const async = require('async');

function doSthWith(url, cb) {
    console.log('doing something with ' + url);
    setTimeout(() => cb(), 2000);
}

const urls = ['https://stackoverflow.com/', 'https://phihag.de/'];
async.each(urls, doSthWith, (err) => {
    if (err) {
        // In practice, likely a callback or throw here
        console.error(err);
    } else {
        console.log('done!');
    }
});

如果您对结果感兴趣,请使用async.map

答案 2 :(得分:0)

当我需要循环承诺时,我会使用我方便的花花公子ploop功能。这是一个例子:

// Function that returns a promise
var searchForNumber = function(number) {
    return new Promise(function(resolve, reject) {
      setTimeout(function() {
        var min = 1;
        var max = 10;
        var val = Math.floor(Math.random()*(max-min+1)+min);

        console.log('Value is: ' + val.toString());        

        return resolve(val);        
      }, 1000);
    });
};

// fn     : function that should return a promise.
// args   : the arguments that should be passed to fn.
// donefn : function that should check the result of the promise
//    and return true to indicate whether ploop should stop or not.
var ploop = function(fn, args, donefn) {
    return Promise.resolve(true)
      .then(function() {
          return(fn.apply(null, args));
      })
      .then(function(result) {
        var finished = donefn(result);
        if(finished === true){
           return result;
        } else {
          return ploop(fn, args, donefn);
        }
    });
};

var searchFor = 4;

var donefn = function(result) {
  return result === searchFor;
};

console.log('Searching for: ' + searchFor);
ploop(searchForNumber, [searchFor], donefn)
  .then(function(val) {
    console.log('Finally found! ' + val.toString());  
    process.exit(0);
  })
  .catch(function(err) {
    process.exit(1);
  });