循环内异步函数的调用层次结构?

Call hierarchy of async functions inside a loop?

有一个异步调用,我正在查询一个服务的数据库,但是这个服务有一次可以输出多少的限制,所以我需要通过结果检查它是否达到了限制发送并重复查询,直到它不发送为止。

同步模型:

var query_results = [];

var limit_hit = true; #While this is true means that the query hit the record limit
var start_from = 0; #Pagination parameter

while (limit_hit) {
    Server.Query(params={start_from : start_from}, callback=function(result){
        limit_hit = result.limit_hit;
        start_from = result.results.length;
        query_result.push(result.results);
    }
}

显然上面的方法不起作用,我在这里看到了一些关于这个问题的其他问题,但是他们没有提到当你需要每次迭代等待最后一次完成而你不这样做时该怎么做事先不知道迭代次数。

如何将上面的异步化?我愿意接受使用 promise/deferred-like 逻辑的答案,但最好是干净的东西。

我可能会想出一种使用 waits/timeouts 来完成此操作的怪异而可怕的方法,但必须有一种干净、聪明和现代的方法来解决它。

另一种方法是"pre-query"事先知道特征的数量,这样你就知道循环的数量,我不确定这是否是正确的方法。

这里我们有时会使用 Dojo,但我找到的例子并没有解释当你有未知数量的循环时该怎么做https://www.sitepen.com/blog/2015/06/10/dojo-faq-how-can-i-sequence-asynchronous-operations/

如果你想使用循环,那么我认为没有 Promises 就没有(干净的)方法。

另一种方法如下:

var query_results = [];

var start_from = 0;

funciton myCallback(result) {  
  if(!result) {
    //first call
    Server.Query({ start_from: start_from}, myCallback);
  } else {
    //repeated call
    start_from = result.results.length
    query_result.push(result.results);
    
    if(!result.limit_hit) {
      //limit has not been hit yet
      //repeat the query with new start value
      Server.Query({ start_from: start_from}, myCallback);
    } else {
        //call some callback function here
    }
  }
}

myCallback(null);

您可以调用此递归,但由于查询是异步的,因此您不应该遇到调用堆栈限制等方面的问题。

在 ES6 环境中使用 promise,您可以使用 async/await。我不确定 dojo 是否可行。

您可以使用生成器函数 Generators 来实现此目的 对于 POC:

一些基础知识 - 你用星号定义一个生成器 * - 它公开了一个下一个函数,该函数 returns 下一个值 - 生成器可以在内部使用 yield 语句暂停,并可以通过调用 next() 在外部恢复 - while (true) 将确保生成器在 limit 达到

之前不会完成
function *limitQueries() {
  let limit_hit = false;
  let start_from  = 0;
  const query_result = [];

  while (true) {
    if (limit_hit) {break;}
    yield Server.Query(params={start_from : start_from}, 
        callback=function* (result) {
        limit_hit = result.limit_hit;
        start_from = result.results.length;
        yield query_result.push(result.results);
    }
  }
}

很明显,生成器函数保持了它自己的状态。生成器函数公开了两个属性{ value, done },你可以这样调用它

const gen = limitQueries();
let results = [];
let next = gen.next();

while(next.done) {
  next = gen.next();
}
results = next.value;

您可能需要触摸 Server.Query 方法来处理生成器回调。希望这可以帮助!干杯!

在编写了速率限制器或队列之前,您不会理解回调;) 诀窍是使用计数器:在异步请求之前递增计数器,并在收到响应时递减它,然后您将知道有多少请求 "in flight".

如果服务器阻塞,您希望将项目放回队列中。

您需要考虑很多事项: 如果进程被杀死,队列会发生什么? 在发送另一个请求之前等待多长时间? 确保回调没有被多次调用! 你应该重试多少次? 放弃之前要等多久? 确保没有多余的东西! (永远不会调用回调)

考虑到所有边缘情况后,您将得到一个相当长且不太优雅的解决方案。但是你可以把它抽象成一个函数! (returns 一个 Promise 或任何你喜欢的东西)。 如果您有用户界面,您还想显示加载栏和一些统计信息!

虽然已经有很多答案,但我仍然相信 async/await 是最干净的方法。

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function

你可能需要 babel

https://babeljs.io/

JS 异步逻辑语法从 callback 更改为 promise 再到 async/await,它们都做同样的事情,当 callback 嵌套很多时我们需要类似的东西一条链,然后 promise 来,当 promise 进入循环时,我们需要一些东西让链更简单,然后 async/await 来。但并不是所有的浏览器都支持新语法,所以babel来把新语法编译成旧语法,然后你就可以用新语法编写代码了。

getData().then((data) => {
  //do something with final data
})

async function getData() {
  var query_results = [];

  var limit_hit = true;
  var start_from = 0;
  
  //when you use await, handle error with try/catch
  try {
    while (limit_hit) {
      const result = await loadPage(start_from)
      limit_hit = result.limit_hit;
      start_from = result.results.length;
      query_result.push(result.results);
    }
  } catch (e) {
    //when loadPage rejects
    console.log(e)
    return null
  }
  return query_result
}

async function loadPage(start_from) {
  //when you use promise, handle error with reject
  return new Promise((resolve, reject) => Server.Query({
    start_from
  }, (result, err) => {
    //error reject
    if (err) {
      reject(err)
      return
    }
    resolve(result)
  }))
}

您每次都必须等待服务器响应。这里封装了一个方法

var query = (function(){
  var results = [];
  var count = 0;
  return function check(fun){
    Server.Query({ start_from: count}, function(d){
      count = d.results.length;
      results.push(d.results);
      if (d.limit_hit && fun) fun(results);
      else check(fun);
    });
  };
})();

// Call here
var my_query = query(function(d){
  // --> retrive all data when limit_hit is true)
});