【问题标题】:Using Promises and Loops?使用承诺和循环?
【发布时间】:2016-08-22 18:53:33
【问题描述】:

我正在从网站提取数据。被拉取的数据是分页的。当向第一页发出请求时,会收到包含nextCursor 的响应。此游标必须用于获取第 2 页的结果。每个页面请求都需要一个nextCursor

我正在努力使用 Promises 来实现这一点,因为我找不到任何循环方式。这就是我假设它在没有 Promise 的情况下工作的方式(未经测试,但它展示了我正在尝试做的事情):

let nextCursor = argv.initalCursor

do {
  let r = request('http://example.com/items.php?cursor=' + nextCursor, function(err, resp, body) {
    if(err) throw new Error(err)

    // Do something with the data

    nextCursor = JSON.parse(body)['nextCursor']
  })
} while(nextCursor)

如您所见,循环中的迭代次数是未知的。它将循环直到响应中没有nextCursor

我想做的是使用 Promises 来实现它。除了我不知道如何创建一个以类似方式工作的循环,因为每个请求都依赖于最后一个。

使用 Promise 会如何工作?

这是我当前的解决方案,当 return self.cursorRequest 时失败。该脚本只是停止执行。

'use strict'

let Promise = require('bluebird')
let _ = require('lodash')

class Event {
  constructor(session) {
    this.session = session
    this.scrapedIDs = [] // I don't like this!
  }

  parseGuestsAndCursor(json, guestType) {
    let ids = json['payload'][guestType]['sections'][2][1].map(function(user) {
      return user['uniqueID']
    })

    return {
      ids: _.uniq(ids),
      cursor: json['payload'][guestType]['cursor']
    }
  }

  cursorRequest(cursor, eventID, guestType) {
    let self = this

    return new Promise(function(resolve, reject) {
      let url = `https://example.com/events/typeahead/guest_list/?event_id=${eventID}&tabs[0]=${guestType}&order[${guestType}]=affinity&bucket_schema[${guestType}]=friends&cursor[${guestType}]=${cursor}&dpr=1&__user=${self.session.uid}&__a=1`

      self.session.request(url, function(err, resp, body) {
        if(err) reject(err)
        let json

        try {
          json = JSON.parse(body.substring(9))
        } catch(err) {
          reject(err)
        }

        resolve(self.parseGuestsAndCursor(json, guestType))
      })
    })
  }

  members(eventID, limit, guestType) {
    let self = this
    let ids = []

    return new Promise(function(resolve, reject) {
      let url = `https://example.com/events/typeahead/guest_list/?event_id=${eventID}&tabs[0]=watched&tabs[1]=going&tabs[2]=invited&order[declined]=affinity&order[going]=affinity&order[invited]=affinity&order[maybe]=affinity&order[watched]=affinity&order[ticket_purchased]=affinity&bucket_schema[watched]=friends&bucket_schema[going]=friends&bucket_schema[invited]=friends&bucket_schema[ticket_purchased]=friends&dpr=1&__user=${self.session.uid}&__a=1`

      self.session.request(url, function(err, resp, body) {
        if(err) reject(new Error(err))
        let json, guests

        try {
          json = JSON.parse(body.substring(9))
          guests = self.parseGuestsAndCursor(json, guestType)
        } catch(err) {
          reject(err)
        }

        self.cursorRequest(guests.cursor, eventID, guestType).then(function(guests) {

          self.scrapedIDs.concat(guests.ids).map(function(user) {
            return user['uniqueID']
          })

          if(guests.cursor) {
            return self.cursorRequest(guests.cursor, eventID, guestType)
          }
          else {
            resolve(self.scrapedIDs)
          }
        })
      })
    })
  }
}

module.exports = Event

【问题讨论】:

  • 我根本看不到任何承诺。
  • @T.J.Crowder 没有,因为我不知道如何实现我想要用承诺做的事情。这就是为什么我要问,这种事情如何用承诺来完成?我不需要解决方案,只需要指向任何可以提供帮助的资源的指针。
  • 您希望 Promise 等到所有数据都检索完毕后再解析?
  • @Bergi 是的,我确实看到了。但是,因为下一个请求取决于上一个请求的数据,所以我觉得它没有用。而通过使用递归得出的解决方案,我能够将所需的数据传递回函数。

标签: javascript node.js promise


【解决方案1】:

由于该过程是异步的,因此您根本不使用循环结构;您只需使用一个调用自身的函数(间接通过另一个函数)。

在你的情况下,既然你说过你想用 Promise 来实现它,那么你就是这样做的(最容易在代码中实际表达);

var p = new Promise(function(resolve, reject) {
    let nextCursor = argv.initialCursor;

    doRequest();

    function doRequest() {
        request('http://example.com/items.php?cursor=' + nextCursor, handleResult);
    }

    function handleResult(err, resp, body) {
        if (err) {
            // Got an error, reject the promise
            reject(err);
        } else {
            // Do something with the data

            // Next?
            nextCursor = JSON.parse(body)['nextCursor'];
            if (nextCursor) {
                // Yup, do it
                doRequest();
            } else {
                // No, we're done
                resolve(/*...resolution value here...*/);
            }
        }
    }
});

(ES2015版本看起来基本一样。)

【讨论】:

  • 这不起作用,因为我收到了Maximum call stack size exceeded
  • @BugHunterUK:您正在运行的版本中一定有错字。您不是偶然在request(..., handleResult); 行中将() 放在handleResult 之后吗?重要的是他们在那里,我们想要传递函数引用,而不是调用它。
【解决方案2】:

另一种方法是承诺您正在使用的异步函数,并以此为基础。

这种方法的好处是功能是模块化的,因此如果您想使用 Promise 发出其他类型的请求,您可以重用 makeRequest() 函数:

let nextCursor = argv.initalCursor

function requestPromise(url) {
    return new Promise(function (resolve, reject) {
        request(url, function (err, resp, body) {
            if (err) { reject(new Error(err)); }
            else { resolve({ resp: resp, body: body}); }
        });
   });
}

function queryCursor(cursor) {
    return requestPromise('http://example.com/items.php?cursor=' + cursor)
        .then(function (result) {
            // do something with result

            var nextCursor = JSON.parse(result.body).nextCursor;

            if (nextCursor) {
                return queryCursor(nextCursor);
            }
        });
}

queryCursor(nextCursor)
    .catch(function (err) {
        // deal with err
    });

【讨论】:

  • 我已经用我当前的解决方案更新了我的问题。我似乎无法让它工作。当我 return 它只是停止脚本。
猜你喜欢
  • 2021-09-07
  • 2017-12-18
  • 2014-03-11
  • 2015-03-23
  • 1970-01-01
  • 2013-04-27
  • 1970-01-01
  • 2020-09-04
  • 2020-05-15
相关资源
最近更新 更多