【问题标题】:forEach with JS Promise issuesforEach 与 JS Promise 问题
【发布时间】:2018-02-21 09:11:49
【问题描述】:

我有一个用户数据库,我想为他们设置经度和纬度。但是,在 6 次以上调用后,我收到错误 400,请求错误。我想这是因为我对 google maps API 的调用太多,所以决定创建一个 setTimeout 函数,所以我会每 1 秒获取一次坐标。

然后我发现我的 forEach 行为很奇怪。这是代码,然后我将解释问题所在。 (我认为相关的部分代码)

let noOfSuccess = 0;
        let noCoords = 0;
        let forEachPromise = new Promise((resolve, reject) => {
            arr.forEach(function (user) {
                console.log('street', user.street)
                let coordPromise = new Promise((resolve, reject) => {

                    let street = user.street;
                    let city = user.city;

                    let address = street.concat(', ').concat(city);

                    const url = `https://maps.googleapis.com/maps/api/geocode/json?address=${address}&key=APIKEY`;

                    setTimeout(function () {
                        let coords = axios.get(url).then((response) => {
                            return response;
                        })
                        resolve(coords);
                    }, 1000);

                })

                coordPromise.then(response => {
                    if (response.data.results[0].types == "street_address") {
                        console.log('adres', response.data.results[0].formatted_address)
                        arrSucc.push(response.data.results[0].formatted_address);
                        noOfSuccess++;
                    } else {
                        arrFail.push(response.data.results[0].formatted_address);
                        noCoords++;
                    }
                    console.log('coordResp', 'succ', noOfSuccess, 'fail', noCoords)
                })
            });

我希望它如何工作: 我从数据库中获取用户,我 console.log 街道名称进行测试。然后我创建一个承诺。在承诺中,我等待 1 秒来调用谷歌 API。收到回复后,我解决了这个承诺。 然后我接受响应,做一些检查并 console.log 发生了什么,无论是成功还是失败。然后我去下一个用户。 然后首选输出: 用户街道 -> google API 调用 -> 记录成功或失败 对所有用户重复。

但是发生的事情是: 它记录用户的所有街道,然后进入承诺,1 秒后立即对 API 进行所有调用,而无需等待 1 秒,然后记录每个用户是否成功或失败。外观:

    now listening for requests
 street Kwiatowa 40
street Kwiatowa 40
street Kwiatowa 43
street Kwiatowa 36
street Kwiatowa 27
street Kwiatowa 42
street Kwiatowa 29
street Kwiatowa 45
(node:5800) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 2): Error: Request failed with status code 400
(node:5800) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
adres Kwiatowa 36, 02-579 Warszawa, Poland
coordResp succ 1 fail 0
adres Kwiatowa 43, 02-579 Warszawa, Poland
coordResp succ 2 fail 0
adres Kwiatowa 40, 02-579 Warszawa, Poland
coordResp succ 3 fail 0
adres Kwiatowa 27, 02-579 Warszawa, Poland
coordResp succ 4 fail 0
adres Kwiatowa 29, Radom, Poland
coordResp succ 5 fail 0
adres Kwiatowa 42, 02-579 Warszawa, Poland
coordResp succ 6 fail 0
adres Kwiatowa 40, 02-579 Warszawa, Poland
coordResp succ 7 fail 0

我做错了什么?我理解 Promise 或 forEach 循环是否有问题?

【问题讨论】:

标签: javascript node.js asynchronous


【解决方案1】:

我会避免将 Array.prototype.forEach 与 Promises 一起使用,因为它是 implementation,它不会等待回调函数完成,然后再迭代到数组中的下一个项目(这解释了你提到的行为)。 我对这种情况的建议是使用带有 async/await 的 for 循环:

async function getDataForUsers () {
    for (let user of arr) {
        console.log('street', user.street)
        const response = await myPromiseFunction(user) // inside this function you can make your api calls with axios and setTimeout
        // do some stuff with response
    }
}

我还会看看bluebird 模块,它可能有一些有趣的方法用于您的用例的不同实现。

【讨论】:

    【解决方案2】:

    有几种方法可以解决这个问题。您可以创建基于 Promise 的解决方案,使用 async/await 或使用 RxJS。

    对于这种情况,我建议稍微重构一下代码并查看 Promise 的功能,您还可以查看 bluebird 库以获得比 Promise 更多的功能。

    核心问题是异步性:您正在连续调用所有 Promises,即使它们尝试等待一秒钟,它们也会立即进入事件队列(比方说)。您需要做的是在处理完每个用户后等待一秒钟。

    • 假设您有一组用户:用户
    • 我们还假设您有一个函数,给定一个用户,它从谷歌地图中获取并返回响应(实际上是解析为该响应的 Promise):fetchUserLocation

    使用单独的 Promises,您可以尝试递归策略

    // The only responsible to know **what** to do with each user
    function processUser(user) {
      console.log(user.street, user.city);
    
      // then method returns a new Promise that is resolved when the passed function is called, also it resolves to the value returned
      return fetchUserLocation.then(function(location) {
        console.log(location);
        // do someStuff after you have fetched the data
      });
    }
    
    // The only responsible to know **when** to do stuff with an user
    function controlFetchAllUsers(users, index, delayMills) {
      var currentUser = users[index];
      var hasNextUser = index < users.length - 1;
    
      processUser(currentUser).then(function() {
        // Once we processed the user, we can now wait one second
        // If there is another user
        if (hasNextUser) {
          setTimeout(
            function() {
              controlFetchAllUsers(users, index + 1, delayMills)
            },
            delayMills
          );
        }
      });
    }
    
    controlFetchAllUsers(users, 0, 1000);
    

    一些注意事项,您可能想知道所有过程何时完成,为此您可以使用包装整个过程的 Promise 并且也许使用延迟模式。

    另一方面,对于这类问题,我真诚地推荐 RxJS(然而,任何反应式编程库都可能真的很有用)

    祝你好运!

    【讨论】:

      猜你喜欢
      • 2017-02-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-12-02
      • 2020-12-19
      • 2018-02-09
      • 2019-06-02
      相关资源
      最近更新 更多