JavaScript:while 循环中的异步方法

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/43064719/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-23 01:39:15  来源:igfitidea点击:

JavaScript: Asynchronous method in while loop

javascriptasynchronouswhile-loop

提问by Step

I'm tackling a project that requires me to use JavaScript with an API method call. I'm a Java programmer who has never done web development before so I'm having some trouble with it.

我正在处理一个项目,该项目要求我通过 API 方法调用使用 JavaScript。我是一名 Java 程序员,之前从未进行过 Web 开发,因此遇到了一些麻烦。

This API method is asynchronous and it's in a while loop. If it returns an empty array, the while loop finishes. Otherwise, it loops. Code:

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

This doesn't work. The loop ends before the value of "done" is updated. I've done some reading up on the subject and it appears I need to use promises or callbacks because the API call is asynchronous, but I can't understand how to apply them to the code I have above.

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

Help would be appreciated!

帮助将不胜感激!

回答by SigmaSoldier

edit:see the bottom, there is the real answer.

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

I encourage you yo use the Promise API. Your problem can be solved using a Promise.allcall:

我鼓励你使用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
});

The syntax is in es6, here is the es5 equivalent (Promise API may be included externally):

语法在 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
});

You can also make your api call return the promise and push it directly to the promise array.

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

If you don't want to edit the api_call_methodyou can always wrap your code in a new promise and call the method resolve when it finishes.

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

edit: I have seen now the point of your code, sorry. I've just realized that Promise.all will not solve the problem.

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

You shall put what you posted (excluding the while loop and the control value) inside a function, and depending on the condition calling it again.

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

Then, all can be wraped inside a promise in order to make the external code aware of this asynchronous execution. I'll post some sample code later with my PC.

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

So the good answer

所以很好的答案

You can use a promise to control the flow of your application and use recursion instead of the while loop:

您可以使用 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(...){}"
 */

回答by Hayko Koryun

If you don't want to use Promisesyou can restructure your code like so:

如果你不想使用,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);
            }
        }
    );
}

回答by Roma Harevich

Also you may try recursion solution.

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

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

回答by Zambonilli

If you don't want to use recursion you can change your whileloop into a for ofloop and use a generator function for maintaining done state. Here's a simple example where the for ofloop will wait for the async function until we've had 5 iterations and then done is flipped to true. You should be able to update this concept to set your done variable to true when your webservice calls have buffered all of your data rows.

如果您不想使用递归,您可以将while循环更改为for of循环并使用生成器函数来维护完成状态。这是一个简单的示例,其中for of循环将等待 async 函数,直到我们进行了 5 次迭代,然后将 done 翻转为 true。当您的 webservice 调用已缓冲所有数据行时,您应该能够更新此概念以将 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!');
});

回答by user7337353

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

回答by Dylan

Here is a solution I came up with. Place this in an async function.

这是我想出的解决方案。把它放在一个异步函数中。


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

回答by tim

sigmasoldier's solutionis correct, just wanted to share the ES6 version with async / await:

sigmasoldier解决方案是正确的,只是想与 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();

回答by Thomas

Your loop won't work, because it is sync, your async task is async, so the loop will finish before the async task can even respond. I'd reccomend you to use Promises to manage async tasks:

你的循环不起作用,因为它是同步的,你的异步任务是异步的,所以循环将在异步任务甚至可以响应之前完成。我建议您使用 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() )
            }
        });
    })
}

now to your polling code:

现在到您的投票代码:

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

Why Promises? Because they do state-management of async operations. Why implement that yourself?

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