forEach with JS Promise问题

时间:2018-02-21 09:11:49

标签: javascript node.js asynchronous

我有一个用户数据库,我想为它们设置经度和纬度。但是,经过6次以上的调用后,我得到错误400,请求错误。我认为这是因为我对谷歌地图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发生了什么,无论是成功还是失败。然后我去找下一个用户。 首选输出: 用户街 - >谷歌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

我做错了什么?我理解promises或forEach循环是否存在问题?

2 个答案:

答案 0 :(得分:0)

我会避免将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模块,该模块针对您的用例可能有一些有趣的方法用于不同的实现。

答案 1 :(得分:0)

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

在这种情况下,我建议稍微重构一下代码,看看Promise的功能,还可以检查bluebird库以在Promises上具有更多功能。

核心问题是异步性:您正在安全地调用所有Promises,即使它们试图等待一秒钟,它们也会一次进入事件队列(比如说)。您要做的是在处理每个用户后等待一秒钟。

  • 假设您有一系列用户:用户
  • 也可以说,您有一个函数,该函数给了一个用户,它从google地图获取并返回响应(实际上是一个解析为该响应的Promise):fetchUserLocation

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

// 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(但是,任何反应式编程库可能都非常有用)

祝你好运!