JavaScript:while循环中的异步方法

Posted

技术标签:

【中文标题】JavaScript:while循环中的异步方法【英文标题】:JavaScript: Asynchronous method in while loop 【发布时间】:2017-08-21 05:47:55 【问题描述】:

我正在处理一个需要我将 javascript 与 API 方法调用一起使用的项目。我是一名 Java 程序员,之前从未做过 Web 开发,所以我遇到了一些麻烦。

此 API 方法是异步的,它处于一个 while 循环中。如果它返回一个空数组,则 while 循环结束。否则,它会循环。代码:

var done = true;

do

    async_api_call(
        "method.name", 
         
            // Do stuff.
        ,
        function(result) 
        
            if(result.error())
            
                console.error(result.error());
            
            else
            
                // Sets the boolean to true if the returned array is empty, or false otherwise.
                done = (result.data().length === 0) ? true : false;
            
        
    );

 while (!done);

这不起作用。循环在“完成”的值更新之前结束。我已经对该主题进行了一些阅读,似乎我需要使用承诺或回调,因为 API 调用是异步的,但我不明白如何将它们应用于我上面的代码。

我们将不胜感激!

【问题讨论】:

你真的需要使用while循环吗?异步的想法。编程是为了避免循环直到完成某些事情,但使用回调(在您的情况下为function(result))来更新 UI。 您的async_api_call 我已经开除,所以它会通过,它不会;不等待回调。这不是异步调用的目的吗?先阅读这篇***.com/questions/748175/…,也许它会阐明异步函数的含义。而且因为您还不能更新 done 变量,所以 !done 是 false 并且会中断 do while 循环。 不幸的是,我需要循环 async_api_call 多次。这是因为有问题的方法只处理50个批次的数据,而我需要处理的项目有数千个,所以我需要继续循环它,直到处理完所有项目。 要理解的重要一点是,内部函数(function(result))将在稍后的某个时间点执行,可能在父函数完成执行很久之后; async_api_call 在异步操作完成并且 then 执行回调之前不会阻塞,它会更早返回并在工作完成时执行回调。已经有一些有帮助的答案,只是想说明一下。 这里可以用es6吗? 【参考方案1】:

编辑:看底部,有真正的答案。

我鼓励你使用Promise API。您的问题可以通过Promise.all 电话解决:

let promises = [];
while(something)
    promises.push(new Promise((r, j) => 
        YourAsyncCall(() => r());
    );

//Then this returns a promise that will resolve when ALL are so.
Promise.all(promises).then(() => 
    //All operations done
);

语法在 es6 中,这里是 es5 等价物(Promise API 可能包含在外部):

var promises = [];
while(something)
    promises.push(new Promise(function(r, j)
        YourAsyncCall(function() r(); );
    );

//Then this returns a promise that will resolve when ALL are so.
Promise.all(promises).then(function()
    //All operations done
);

您也可以让您的 api 调用返回 promise 并将其直接推送到 promise 数组。

如果您不想编辑 api_call_method,您可以随时将代码包装在新的 Promise 中,并在完成时调用方法 resolve。

编辑:我现在看到了你代码的重点,抱歉。我刚刚意识到 Promise.all 不能解决问题。

您应该将您发布的内容(不包括while循环和控制值)放入一个函数中,并根据条件再次调用它。

然后,所有内容都可以包装在一个 Promise 中,以便让外部代码知道这种异步执行。稍后我会在我的电脑上发布一些示例代码。

所以很好的答案

您可以使用 Promise 来控制应用程序的流程并使用递归而不是 while 循环:

function asyncOp(resolve, reject) 
    //If you're using NodeJS you can use Es6 syntax:
    async_api_call("method.name", , (result) => 
      if(result.error()) 
          console.error(result.error());
          reject(result.error()); //You can reject the promise, this is optional.
       else 
          //If your operation succeeds, resolve the promise and don't call again.
          if (result.data().length === 0) 
              asyncOp(resolve); //Try again
           else 
              resolve(result); //Resolve the promise, pass the result.
          
      
   );


new Promise((r, j) => 
    asyncOp(r, j);
).then((result) => 
    //This will call if your algorithm succeeds!
);

/*
 * Please note that "(...) => " equivals to "function(...)"
 */

【讨论】:

如果你的目标是 es6,那么使用异步等待。这里的关卡太多 @EduardJacko 是的 async/await 非常适合这些东西,但是当创建答案时 async/await 我不知道它 实际上是他的 async_api_call 接受回调。无论如何,您都无法使用异步等待。对不起。【参考方案2】:

如果您不想使用递归,您可以将 while 循环更改为 for of 循环并使用生成器函数来维护完成状态。这是一个简单的示例,其中for of 循环将等待异步函数,直到我们进行了 5 次迭代,然后将 done 翻转为 true。当您的 Web 服务调用缓冲了所有数据行时,您应该能够更新此概念以将您的 done 变量设置为 true。

let done = false;
let count = 0;
const whileGenerator = function* () 
    while (!done) 
        yield count;
    
;

const asyncFunction = async function()
    await new Promise(resolve =>  setTimeout(resolve); );
;
const main = new Promise(async (resolve)=>
    for (let i of whileGenerator())
       console.log(i);
       await asyncFunction();

       count++;
       if (count === 5)
           done = true;
       
    
    resolve();
);
main.then(()=>
    console.log('all done!');
);

【讨论】:

【参考方案3】:

您也可以尝试递归解决方案。

function asyncCall(cb) 
// Some async operation


function responseHandler(result) 
    if (result.error()) 
        console.error(result.error());
     else if(result.data() && result.data().length) 
        asyncCall(responseHandler);
    


asyncCall(responseHandler);

【讨论】:

【参考方案4】:

sigmasoldier的solution是对的,只是想用async/await分享ES6版本:

const asyncFunction = (t) => new Promise(resolve => setTimeout(resolve, t));

const getData = async (resolve, reject, count) => 

    console.log('waiting');
    await asyncFunction(3000);
    console.log('finshed waiting');

    count++;

    if (count < 2) 
        getData(resolve, reject, count);
     else 
        return resolve();
    


const runScript = async () => 
    await new Promise((r, j) => getData(r, j, 0));
    console.log('finished');
;

runScript();

【讨论】:

漂亮,这引导我完成了我需要做的事情。我的特殊情况是我需要通过异步调用“循环”,但每个连续调用都取决于前一个调用返回的一些数据。添加更多变量并根据我的需要修改计数我让它工作。我唯一不完全理解的是有必要将 getData() 本身包装在一个 promise 中,如果 getData 本身返回一个 promise(因为 async func),为什么我们不能直接调用它并期望相同的结果? 我认为你是对的@xunux。不需要 promise,因为 async 函数应该原生返回一个 promise。 这对我来说是有意义的,但我需要这样做才能让它工作,我认为它是从所需的原始包装承诺中传递解析和拒绝功能,因为如果我刚刚做了await getData(),即使它返回了一个承诺,我也无法通过它的解决和拒绝来结束每个循环【参考方案5】:

如果你不想使用Promises,你可以像这样重构你的代码:

var tasks = [];
var index = 0;

function processNextTask()

    if(++index == tasks.length)
    
        // no more tasks
        return;
    

    async_api_call(
        "method.name", 
         
            // Do stuff.
        ,
        function(result) 
        
            if(result.error())
            
                console.error(result.error());
            
            else
            
                // process data
                setTimeout(processNextTask);
            
        
    );

【讨论】:

【参考方案6】:

你的循环不会工作,因为它是同步的,你的异步任务是异步的,所以循环将在异步任务响应之前完成。我建议你使用 Promises 来管理异步任务:

//first wrapping your API into a promise
var async_api_call_promise = function(methodName, someObject)
    return new Promise((resolve, reject) => 
        async_api_call(methodName, someObject, function(result)
            if(result.error()) 
                reject( result.error() ) 
            else
                resolve( result.data() )
            
        );
    )

现在到您的投票代码:

//a local utility because I don't want to repeat myself
var poll = () => async_api_call_promise("method.name", /*Do stuff.*/);

//your pulling operation
poll().then(
    data => data.length === 0 || poll(),  //true || tryAgain
    err => 
        console.error(err);
        return poll();
    
).then((done) => 
    //done === true
    //here you put the code that has to wait for your "loop" to finish
);

为什么要承诺?因为他们对异步操作进行状态管理。为什么要自己实现?

【讨论】:

【参考方案7】:
  let taskPool = new Promise(function(resolve, reject) 
    resolve("Success!");
  );
  let that = this;
  while (index < this.totalPieces) 
    end = start + thisPartSize;
    if (end > filesize) 
      end = filesize;
      thisPartSize = filesize - start;
    
    taskPool.then(() => 
      that.worker(start, end, index, thisPartSize);
    );
    index++;
    start = end;
  

【讨论】:

【参考方案8】:

这是我想出的解决方案。将其放在异步函数中。


        let finished = false;
        const loop = async () => 
            return new Promise(async (resolve, reject) => 
                const inner = async () => 
                    if (!finished) 
                        //insert loop code here
                        if (xxx is done)  //insert this in your loop code after task is complete
                           finshed = true;
                           resolve();
                         else 
                           return inner();
                        
                    
                
                await inner();
            )
        
        await loop();

【讨论】:

以上是关于JavaScript:while循环中的异步方法的主要内容,如果未能解决你的问题,请参考以下文章

异步While循环?

iOS:在while循环中带有块回调的异步方法

While 循环使用 Await Async。

循环javascript时的回调方法

Python中的用for,while循环遍历文件实例

JavaScript 中的 While 循环与 For 循环? [关闭]