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

时间:2021-12-27 22:04:25

There's a async call I'm making that queries a database on a service, but this service has a limit of how many it can output at once, so I need to check if it hit its limit through the result it sends and repeat the query until it doesn't.

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

Synchronous mockup :

同步模型:

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);
    }
}

Obviously the above does not work, I've seen some other questions here about the issue, but they don't mention what to do when you need each iteration to wait for the last one to finish and you don't know before hand the number of iterations.

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

How can I turn the above asynchronous? I'm open to answers using promise/deferred-like logic, but preferably something clean.

我怎样才能打开上面的异步?我愿意接受使用promise / deferred-like逻辑的答案,但最好是干净利落的东西。

I can probably think of a monstruous and horrible way of doing this using waits/timeouts, but there has to be a clean, clever and modern way to solve it.

我可能会想到使用等待/超时这样做的一种奇怪而可怕的方式,但必须有一种干净,聪明和现代的方法来解决它。

Another way is to make a "pre-query" to know the number of features before hand so you know the number of loops, I'm not sure if this is the correct way.

另一种方法是做一个“预查询”以了解事前的特征数量,这样你就知道循环次数了,我不确定这是不是正确的方法。

Here we use Dojo sometimes, but the examples I found does not explain what to do when you have an unknown amount of loops https://www.sitepen.com/blog/2015/06/10/dojo-faq-how-can-i-sequence-asynchronous-operations/

这里我们有时会使用Dojo,但是我发现的例子并没有解释当你有一个未知数量的循环时该怎么做https://www.sitepen.com/blog/2015/06/10/dojo-faq-how-can -i-序列异步的操作/

5 个解决方案

#1


3  

If you want to use a loop then I think there is no (clean) way to do it without Promises.

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

A different approach would be the following:

不同的方法如下:

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);

You could call this recursive, but since the Query is asynchronous you shouldn't have problems with call stack limits etc.

您可以将此调用称为递归,但由于Query是异步的,因此您不应该遇到调用堆栈限制等问题。

Using promises in an ES6 environment you could make use of async/await. Im not sure if this is possible with dojo.

在ES6环境中使用promises,您可以使用async / await。我不确定这是否可能与道场。

#2


4  

although many answers already, still I believe async/await is the cleanest way.

虽然已有很多答案,但我仍然认为async / await是最干净的方式。

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

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

and you might need babel

你可能需要巴贝尔

https://babeljs.io/

https://babeljs.io/

JS async logic syntax changed from callback to promise then to async/await, they all do the same thing, when callback nests a lot we need something like a chain, then promise come, when promise goes in loop, we need something make the chain more plain more simple, then async/await come. But not all browsers support the new syntax, so babel come to compile new syntax to old syntax, then you can always code in new syntax.

JS异步逻辑语法从回调变为承诺再变为异步/等待,它们都做同样的事情,当回调嵌套很多时我们需要类似链的东西,然后承诺来,当承诺进入循环时,我们需要一些东西做链更平原更简单,然后异步/等待来。但并非所有浏览器都支持新语法,因此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)
  }))
}

#3


2  

You don't understand callbacks until you have written a rate limiter or queue ;) The trick is to use a counter: Increment the counter before the async request, and decrement it when you get the response, then you will know how many requests are "in flight".

在编写速率限制器或队列之前,您不了解回调;)技巧是使用计数器:在异步请求之前递增计数器,并在获得响应时递减计数器,然后您将知道有多少请求“在飞行中”。

If the server is choked you want to put the item back in the queue.

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

There are many things you need to take into account: What will happen to the queue if the process is killed ? How long to wait before sending another request ? Make sure the callback is not called many times ! How many times should you retry ? How long to wait before giving up ? Make sure there are no loose ends ! (callback is never called)

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

When all edge cases are taken into account you will have a rather long and not so elegant solution. But you can abstract it into one function! (that returns a Promise or whatever you fancy). If you have a user interface you also want to show a loading bar and some statistics!

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

#4


0  

You must await for the server response every time. Here a encapsulated method

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

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)
});

#5


-1  

You can use a generator function Generators to achieve this For POC:

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

some basics - You define a generator with an asterick * - it exposes a next function which returns the next value - generators can pause with yield statement internally and can resume externally by calling the next() - While (true) will ensure that the generator is not done until limit has reached

一些基础知识 - 你定义一个带有星号的生成器* - 它暴露了一个返回下一个值的下一个函数 - 生成器可以在内部暂停yield语句,并可以通过调用next()从外部恢复 - 而(true)将确保生成器在达到限制之前不会完成

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);
    }
  }
}

So apparently, the generator function maintains its own state. Generator function exposes two properties { value, done } and you can call it like this

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

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

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

You might have to touch your Server.Query method to handle generator callback. Hope this helps! Cheers!

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

#1


3  

If you want to use a loop then I think there is no (clean) way to do it without Promises.

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

A different approach would be the following:

不同的方法如下:

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);

You could call this recursive, but since the Query is asynchronous you shouldn't have problems with call stack limits etc.

您可以将此调用称为递归,但由于Query是异步的,因此您不应该遇到调用堆栈限制等问题。

Using promises in an ES6 environment you could make use of async/await. Im not sure if this is possible with dojo.

在ES6环境中使用promises,您可以使用async / await。我不确定这是否可能与道场。

#2


4  

although many answers already, still I believe async/await is the cleanest way.

虽然已有很多答案,但我仍然认为async / await是最干净的方式。

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

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

and you might need babel

你可能需要巴贝尔

https://babeljs.io/

https://babeljs.io/

JS async logic syntax changed from callback to promise then to async/await, they all do the same thing, when callback nests a lot we need something like a chain, then promise come, when promise goes in loop, we need something make the chain more plain more simple, then async/await come. But not all browsers support the new syntax, so babel come to compile new syntax to old syntax, then you can always code in new syntax.

JS异步逻辑语法从回调变为承诺再变为异步/等待,它们都做同样的事情,当回调嵌套很多时我们需要类似链的东西,然后承诺来,当承诺进入循环时,我们需要一些东西做链更平原更简单,然后异步/等待来。但并非所有浏览器都支持新语法,因此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)
  }))
}

#3


2  

You don't understand callbacks until you have written a rate limiter or queue ;) The trick is to use a counter: Increment the counter before the async request, and decrement it when you get the response, then you will know how many requests are "in flight".

在编写速率限制器或队列之前,您不了解回调;)技巧是使用计数器:在异步请求之前递增计数器,并在获得响应时递减计数器,然后您将知道有多少请求“在飞行中”。

If the server is choked you want to put the item back in the queue.

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

There are many things you need to take into account: What will happen to the queue if the process is killed ? How long to wait before sending another request ? Make sure the callback is not called many times ! How many times should you retry ? How long to wait before giving up ? Make sure there are no loose ends ! (callback is never called)

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

When all edge cases are taken into account you will have a rather long and not so elegant solution. But you can abstract it into one function! (that returns a Promise or whatever you fancy). If you have a user interface you also want to show a loading bar and some statistics!

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

#4


0  

You must await for the server response every time. Here a encapsulated method

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

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)
});

#5


-1  

You can use a generator function Generators to achieve this For POC:

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

some basics - You define a generator with an asterick * - it exposes a next function which returns the next value - generators can pause with yield statement internally and can resume externally by calling the next() - While (true) will ensure that the generator is not done until limit has reached

一些基础知识 - 你定义一个带有星号的生成器* - 它暴露了一个返回下一个值的下一个函数 - 生成器可以在内部暂停yield语句,并可以通过调用next()从外部恢复 - 而(true)将确保生成器在达到限制之前不会完成

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);
    }
  }
}

So apparently, the generator function maintains its own state. Generator function exposes two properties { value, done } and you can call it like this

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

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

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

You might have to touch your Server.Query method to handle generator callback. Hope this helps! Cheers!

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