4

我有来自 MongoDB 的 13.000 个文档,其中有地址行 + 邮政编码,我试图为每个文档向 Google 的地理编码 API 发出请求,并为它们获取 LAT + LONG,以便我可以让它们动态出现在地图搜索中。

我设计了以下 for of 循环,一次测试 10 个项目,但由于写入 DB 调用和调用 API 的异步性质,来自 HTTPS 请求的 LAT/LONG 坐标最终未定义/unavailable to knex's INSERT 并且循环似乎一直在继续......

是否可以以阻塞方式编写它?因此,除非两个承诺都已解决,否则 for 循环不会转到下一项?

编码:

let results = [];
  await forLoop();

  async function forLoop() {
    for (job of allJobs) {
      const geoData = await getGeoData(
        job.site.addressLine1,
        job.site.postcode
      );
      const dbResult = await addToDb(geoData);
      results.push(dbResult);

      async function getGeoData(addressLine1, postcode) {
        const friendlyAddress = encodeURIComponent(addressLine1 + ' ' + postcode);
        https
          .get(
            'https://maps.googleapis.com/maps/api/geocode/json?key=<API_KEY_IGNORE_THIS_ITS_HARDCODED_IN_MY_REAL_CODE>&address=' +
              friendlyAddress,
            resp => {
              let data = '';
              resp.on('data', chunk => {
                data += chunk;
              });
              // The whole response has been received. Print out the result.
              resp.on('end', () => {
                console.log(JSON.parse(data).explanation);
                let result = JSON.parse(data);
                return result;
              });
            }
          )
          .on('error', err => {
            console.log('Error: ' + err.message);
          });
      }

      async function addToDb(geoData) {
        try {
          await knex('LOCATIONS')
            .returning('*')
            .insert({
              UPRN: job.site.UPRN,
              lat: geoData.results[0].geometry.location.lat,
              lng: geoData.results[0].geometry.location.lng
            });
        } catch (err) {
          err.name = 'database';
          next(err);
        }
      }
    }
  }
  res.send(results);

我确保代码库没有空值,并测试了 api 调用和数据库调用,以确保它们独立工作。

4

2 回答 2

1

只需使用承诺(或回调)

我知道每个人都讨厌 JavaScript,所以这些反惯用的转译器和新的语言“特性”的存在使 JavaScript 看起来像 C# 之类的,但老实说,按照最初设计的方式使用语言更容易(否则使用 Go或其他一些实际上以您想要的方式运行的语言 - 并且无论如何性能更高)。如果您必须在应用程序中公开 async/await,请将其放在界面上,而不是到处乱扔垃圾。

我的 2 美分。

我将编写一些伪代码来向您展示这有多容易:

function doItAll(jobs) {
  var results = [];

  function next() {
    var job = jobs.shift();
    if (!job) {
      return Promise.resolve(results);
    }
    return makeRequest(job.url).then(function (stuff) {
      return updateDb().then(function (dbStuff) {
        results.push(dbStuff);
      }).then(next);
    });
  }

  return next();
}

function makeRequest() {
  return new Promise(function (resolve, reject) {
    var resp = http.get('...', {...});
    resp.on('end', function () {
      // ... do whatever
      resolve();
    });
  });
}

简单的。易于阅读。代码的样子和实际发生的事情之间 1:1 的对应关系。不要试图“强制”JavaScript 的行为与它的设计方式背道而驰。

你学习理解异步代码的时间越长,理解它的时间就越长。

只需潜入并学习“以 JavaScript 方式”编写 JavaScript!:D

于 2019-03-30T15:32:02.830 回答
0

这是我更新的函数,它可以正常和同步地工作,一个一个地获取数据并将其添加到数据库中,然后再移动到下一个。

我通过自定义@coolAJ86 答案做到了这一点,并且我已将其标记为正确的答案,但我认为这对偶然发现此线程的人们查看我的最终、工作和测试版本会有所帮助。

var geoApiUrl =
    'https://maps.googleapis.com/maps/api/geocode/json?key=<<MY API KEY>>&address=';

  doItAll(allJobs)

  function doItAll(jobs) {
    var results = [];
    var errors = [];

    function nextJob() {
      var job = jobs.shift();
      if (!job) {
        return Promise.resolve(results);
      }
      var friendlyAddress =
        geoApiUrl +
        encodeURIComponent(job.addressLine1 + ' ' + job.postcode);

      return makeRequest(friendlyAddress).then(function(result) {
        if((result.results[0] === undefined) || (result.results[0].geometry === undefined)){
          nextJob();
        } else { 
        return knex('LOCATIONS')
          .returning('*')
          .insert({
            UPRN: job.UPRN,
            lat: result.results[0].geometry.location.lat,
            lng: result.results[0].geometry.location.lng,
            title: job.title,
            postcode: job.postcode,
            addressLine1: job.addressLine1,
            theo_id: job.clientId
          })
          .then(function(data) {
            // console.log('KNEX CALLBACK COMING')
            // console.log(data[0])
            console.log(data[0]);
            results.push(data[0]);
            nextJob();
          })
          .catch(function(err) {
            console.log(err);
            errors.push(job);
          });
        }
      });
    }
    return nextJob();
  }

  function makeRequest(url) {
    return new Promise(function(resolve, reject) {
      https
        .get(url, resp => {
          let data = '';
          resp.on('data', chunk => {
            data += chunk;
          });
          // The whole response has been received. Print out the result.
          resp.on('end', () => {
            let result = JSON.parse(data);
            resolve(result);
          });
        })
        .on('error', err => {
          console.log('Error: ' + err.message);
          reject(err);
        });
    });
  }
于 2019-04-01T11:16:33.707 回答