reqwest icon indicating copy to clipboard operation
reqwest copied to clipboard

Promises returned from .catch aren't honored

Open appsforartists opened this issue 10 years ago • 0 comments

I'm calling an API recursively to build a tree. Sometimes, I get throttled and need to exponentially back-off. I can't do this with Reqwest natively, because anything I return from .catch falls on the floor.

I was able to fix this by wrapping the reqwest instance in Promise.resolve, which gives me a Node-native Promise instead of Reqwest's thennable quasi-promise.

The solution looks like this:

  function buildTree (itemNodes) {
    itemNodes = Array.prototype.slice.apply(itemNodes);

    return Promise.all(
      Lazy(itemNodes).map(
        function (itemNode, i, itemNodes) {
          // Exponential back-off settings
          var nextDelay =  1000;
          var MAX_DELAY = 16000;

          var attemptRequest = function () {
            // Reqwest's .catch method won't let us save the promise when there's a
            // recoverable error (like a 503); however, we can fall back to Node's
            // implementation by wrapping the Reqwest in Promise.resolve
            return Promise.resolve(
              new Reqwest(
                {
                  "url":      // URL for this branch of the tree…
                }
              )
            ).then(
              function (childNode) {
                childNode = domify(childNode);

                var childItemsNode = childNode.querySelector("category > items");
                var childItemNodes = childNode.querySelectorAll("category > items > item");

                // if childItemsNode is owned by another document, we can't append it to itemNode
                childItemsNode._ownerDocument = null;
                itemNode.appendChild(childItemsNode);

                return buildTree(childItemNodes);
              }
            ).catch(
              function (response, message, error) {
                // Exponentially back-off if we've been rate-limited
                if (response.status == 503 && nextDelay <= MAX_DELAY) {
                  // Add a fraction of a second to the wait time to make sure parallel retries don't collide
                  return Promise.delay(nextDelay + 1000 * Math.random()).then(
                    function () {
                      nextDelay *= 2;

                      return attemptRequest();
                    }
                  );

                } else {
                  if (error && error.stack) {
                    console.error(error.stack);

                  } else {
                    console.error("Error in buildTree:");
                    console.error({"url": response._url.href, "status": response.status, "content": response.response});
                  }
                }
              }
            );
          };
          // …
          return attemptRequest();
        }
      ).compact().toArray()
    ).then(


      // This was never called if the API failed before I wrapped with Promise.resolve


      function () {
        // Return the same data we received, after we've processed it
        return itemNodes;
      }
    );
  };
};

appsforartists avatar Oct 22 '14 00:10 appsforartists