如何让JavaScript循环在开始下一次迭代之前等待当前迭代完成?

3
我有一个循环,会被迭代1000次,每次迭代都会发出请求,然后打印该请求的结果。类似于下面的示例。
let start = console.log(Date.now())
for (i = 0; i < 1000; i++) {
    request.then(data => {
        console.log(Date.now() - start)
    })
}

然而,这会导致第一个请求完成的时间比只有一次循环的时候要长。

当i小于1000时:

5860
...

当 i 小于 1 时:

220,
...

为了这个脚本的目的,然而我想在开始下一次循环之前等待收到每个结果。

1
你确定要一个接一个地发出请求吗? - MauriceNino
1
FYI:最好将 i 定义为 let,以防止它被创建为全局变量。for(let i = 0; i < 1000; i++)... - Reyno
@MauriceNino 是的,我不得不这样做。我的脚本是为一个交易机器人而设计的,在其中它需要在3秒内尽可能多地发出请求。如果它需要5秒钟才能返回第一个请求,那么它就没有用了。 - Baxter Cooper
2个回答

11

如果你想坚持使用ES5处理Promises的方式,你可以采用以下方法:

const start = console.log(Date.now())

// Create a instantly resolved promise
for (let i = 0, p = Promise.resolve(); i < 1000; i++) {
  // append new promise to the chain
  p = p.then(() => request())
       .then(() => console.log(Date.now() - start));
}

如果您能使用ES6方法,您可以像下面这样使用 async/await 模式编写:

const asyncLoop = async () => {
  const start = console.log(Date.now())

  for (let i = 0; i < 1000; i++) {
    const data = await request();
    console.log(Date.now() - start);
  }
}

asyncLoop();

实际上,你想要一个接着一个地发出请求的机会是非常小的,所以如果你想要同时发出请求,但在所有请求都解析后再执行某些操作,你可以使用 Promise.all(...)

const start = console.log(Date.now())
const requests = [request1, request2, ...];

Promise.all(requests)
       .then(() => console.log(Date.now() - start));

1
哇,我从来不知道你可以通过使用p = Promise.resolve()来欺骗执行堆栈来黑掉包含.then()for循环!无论如何,async/await更加简洁。 - Jeremy Thille
@JeremyThille 强烈同意!async/await是首选方式。如果不允许使用ES6代码,我强烈建议使用像Typescript或Babel这样的工具将ES6代码(易于维护)编译为ES5代码(在IE中运行),而不是手动编写ES5代码。 - MauriceNino
是的,"我们必须支持IE"时代终于要结束了,因为微软宣布他们正式放弃IE。所以,ES6+就是未来的趋势。如果有人让我开发一个必须在IE10上运行的应用程序,我会友好地建议他们寻找其他开发者。 - Jeremy Thille
@JeremyThille 只有当最后一个人停止使用IE时,它才会消失。尽管我很讨厌它,但一些开发人员不得不支持它,因为他们在政府或类似机构工作,那里的用户太老了,无法做出改变。对于任何其他类型的工作,我完全支持你的观点。 - MauriceNino
谢谢。我非常喜欢使用ES6方法,这是我尝试其他方法时得到的。这是我第一次使用JavaScript,所以我正在摸索。脚本必须在3秒内完成尽可能多的请求,因此我必须按顺序执行它们。 - Baxter Cooper

-1

你可以在这里使用异步等待模式。 您可以通过更改代码来实现此目的

async function iteration() {
    let start = console.log(Date.now())
    for (let i = 0; i < 1000; i++) {
        const data = await httpRequest()
        console.log(Date.now() - start)
    }

}

async function httpRequest() {
    return new Promise((resolve, reject) => {
        request.then(data => {
            //Do anything you want to do with data
            resolve(data)
        }).catch(error => {
            console.error(`Error in request`)
            reject(error)
        })
    })
}

解释:

  1. 我将请求代码移动到一个单独的函数httpRequest中,并且该函数会在成功时返回promise
  2. 我使用await关键字在for循环中调用了httpRequest,现在循环将等待请求完成
  3. 我还将for循环代码包装在函数中,因为您只能在async函数中使用await

“request”已经返回了一个Promise,所以你不需要将它包装在另一个Promise内。基本上,你整个的httpRequest()函数可以被重写为request。你只需要这样做:const data = await request(),并且移除整个无用的httpRequest()函数。此外,为什么要混合使用旧的.then()类Promise语法和现代的async/await语法呢?完全摆脱.then()即可。 - Jeremy Thille
基本上,1.我已经将请求代码移动到一个名为httpRequest的单独函数中,并且该函数将在成功时返回Promise --> 为什么? :) 您只是通过在另一个Promise中包装Promise来添加不必要的代码、复杂性和旧语法! - Jeremy Thille
@JeremyThille 你说得对。我只是想添加一些额外的日志或其他东西。但是,你说得很有道理。 - arpanexe

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接