【问题标题】:In Node, how do I request JSON from multiple URLs using promises?在 Node 中,如何使用 Promise 从多个 URL 请求 JSON?
【发布时间】:2017-06-06 13:38:20
【问题描述】:

请原谅这个相当具体的问题,尽管我认为一般的最终目标可能对其他人有用。

目标:使用从多个 JSON API URL 请求的数据填充 MongoDB。

简短的问题:到目前为止,我在使用 Bluebird 的 request-promise 方面取得了一些成功:

var rp = require('request-promise');
var options = {
    uri: 'http://www.bbc.co.uk/programmes/b006qsq5.json',
    headers: {
        'User-Agent': 'Request-Promise'
    },
    json: true
};

rp(options)
    .then(function (body) {
        // Mongoose allows us query db for existing PID and upsert
        var query = {pid: body.programme.pid},
            update = {
                name: body.programme.title,
                pid: body.programme.pid,
                desc: body.programme.short_synopsis
            },
            options = { upsert: true, new: true };

        // Find the document
        Programme.findOneAndUpdate(query, update, options, function(err, result) {
            if (err) return res.send(500, { error: err });
            return res.send("succesfully saved");
        });
    })
    .catch(function (err) {
        return res.send(err);
    })

但是,如果任何承诺被拒绝,我如何循环访问一组 URL,而不会使程序失败? 例如,如果任何 URL 错误,使用 Bluebird 会失败。

const urls = ['http://google.be', 'http://google.uk']

Promise.map(urls, rp)
  .map((htmlOnePage, index) => {
    return htmlOnePage;
  })
  .then(console.log)
  .catch((e) => console.log('We encountered an error' + e));

因为我想用成功的请求写入数据库,并忽略那些可能没有立即响应的请求,所以我需要一些东西来跳过被拒绝的承诺,而 .all 不会这样做。

长问题: 我整天都在阅读有关承诺的内容,这让我很头疼!但是我找到了一些不错的资源,比如https://pouchdb.com/2015/05/18/we-have-a-problem-with-promises.html,里面提到了Promise factory的使用。这对我的情况有用吗?我最初认为我应该发出每个请求,处理结果并将其添加到数据库中,然后继续下一个请求;但是看到.all 我想我应该做所有的请求,把结果保存在一个数组中,然后用我的数据库保存函数循环。

我什至应该为此使用 Promise 吗?也许我应该只使用 async.js 之类的东西并按顺序运行我的请求。

非常感谢任何帮助或想法。

【问题讨论】:

  • 如果请求不相互依赖,没有理由需要 all() 进行数据库更新
  • 我认为您可以将 async.eachSeries 与 promise 一起使用。即使发生错误,循环也不会停止caolan.github.io/async/docs.html#eachOfSeries
  • @AsifSaeed 不建议将 Promise 与 async.js 混合使用

标签: javascript node.js mongodb promise bluebird


【解决方案1】:

但是,如果任何承诺被拒绝,我如何循环访问一组 URL,而不会导致程序失败?

如果你从 .catch 返回一个值而不是被拒绝的承诺,你将返回一个已解决的承诺

所以,对于每个单独的请求,您的 .then 可能会返回一个对象,例如

{
    success: true,
    result: whateverTheResultIs
}

你的收获回来了

{
    success: false,
    error: whateverTheErrorIs
}

你真的不需要success属性,虽然它很方便

所以代码是 - 假设 process(url) 返回一个 Promise

Promise.map(urls, url => 
    process(url)
    .then(result => ({result, success:true}))
    .catch(error => ({error, success:false}))
)
.then(results => {
    let succeeded = results.filter(result => result.success).map(result => result.result);
    let failed = results.filter(result => !result.success).map(result => result.error);
});

或者,在 ES5 中

Promise.map(urls, function (url) {
    return process(url).then(function (result) {
        return { result: result, success: true };
    }).catch(function (error) {
        return { error: error, success: false };
    });
}).then(function (results) {
    var succeeded = results.filter(function (result) {
        return result.success;
    }).map(function (result) {
        return result.result;
    });
    var failed = results.filter(function (result) {
        return !result.success;
    }).map(function (result) {
        return result.error;
    });
});

【讨论】:

  • 非常感谢这个非常全面的解释!代码读起来很好,对我来说很有意义。
【解决方案2】:

我不知道这是否适合您的情况,但我认为您可以使用计数器来检查所有承诺何时返回,而不管每个承诺是否已解决或拒绝

var heroes = [
  'Superman',
  'Batman',
  'Spiderman',
  'Capitan America',
  'Ironman',
];

function getHero(hero) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      return Math.round(Math.random()) ? resolve(hero + ' lives') : reject(hero + ' dead');
    }, Math.random() * 3000)    
  })
}

function checkHeroes() {
  var checked = heroes.length;
  heroes.forEach((hero) => {
    getHero(hero)
    .then((res) => {
      checked --;
      console.log(res);
      if (!checked) done();   
    })
    .catch((err) => { 
      checked --;
      console.log(err);
      if (!checked) done();      
    });           
  }) 
}

function done() {
  console.log('All heroes checked');
}

checkHeroes();

【讨论】:

  • 使用 Promise 的一个要点是,您可以使用带有多个 Promise 的 Promise.all() 之类的工具,而不是自己设计计数器来重建已经存在的功能。当你没有承诺时,可能会使用这样的计数器,但当你有承诺时,真的不应该这样做。此外,由于这是 Bluebird 承诺,因此有像 Promise.map() 这样的工具会为您迭代一个集合并返回一个跟踪所有结果的承诺。
  • 我只是坚持标准的 Promises 实现。
  • Promise.all() 是标准的承诺实现。但是,该问题专门用 Bluebird 标记并使用 Bluebird,因此最佳答案将利用它简化事情的优势。
【解决方案3】:

我认为您的问题与其说是 bluebird api,不如说是构建您的承诺链。

const reducePropsToRequests = (props) => Promise.resolve(Object
  .keys(props)
  .reduce((acc, key) => {
    acc[key] = request(sources[key]);
    return acc;
  }, {}));

const hashToCollection = (hash) => Promise.resolve(Object
  .keys(hash)
  .reduce((acc, k) => {
    return [...acc, {source: k, data: hash[k]}];
  }, []));

const fetchFromSources = (sources) => Promise.props(sources);

const findSeveralAndUpdate = (results) => Promise
  .each(results.map(obj => {
    // you have access to original {a: 'site.com'}
    // here, so use that 'a' prop to your advantage by abstracting out
    // your db config somewhere outside your service
    return Programme.findOneAndUpdate(someConfig[obj.source], obj.data);
  }))

const requestFromSeveralAndUpdate = (sources) => reducePropsToRequests(sources)
  .then(fetchFromSources)
  .then(hashToCollection)
  .then(findSeveralAndUpdate)
  .catch(/* some err handler */);

requestFromSeveralAndUpdate({ a: 'site.com', b: 'site.net' });

【讨论】:

  • 您好,感谢您的回答,我正在寻找更好的结构。我只是在获取所有这些信息时遇到了一些麻烦。我在request(sources[key]) 上遇到了一个错误,应该是props[key] 吗?此外,它似乎没有处理错误的请求(例如 404),我不太清楚我会在哪里处理它。谢谢
【解决方案4】:

我只是使用 request 并编写我自己的 promise,其中包含 try catch 只能解决。下面的伪例子

var request = require('request')

var urls = ['http://sample1.com/json', 'http://sample2.com/json']

var processUrl = (url) => { 
   return new Promise((resolve,reject)=> {
     var result;
     try {
        var myRequest = {
           uri: url,
           method: 'GET',
           header: {...}
        };
        request(option, (res,body,err)=> {
            if(err) {
               result = err;
               return;
            }
            result = body;
        })
     }
     catch(e) {
         result = e;
     }
     finally {
        resolve(result)
     }
  })
}

【讨论】:

    猜你喜欢
    • 2012-09-24
    • 1970-01-01
    • 2015-12-04
    • 2019-08-22
    • 1970-01-01
    • 2015-08-30
    • 1970-01-01
    • 2020-10-06
    • 2019-01-02
    相关资源
    最近更新 更多