Using Q to conduct polling asynchronously with promises

I am facing a situation similar to the one discussed in this blog post: Polling with promises. The author explains using promises for polling until a JobID is returned. I intend to implement this using Q.

I want to chain promises together but my attempts with Q.delay() have not been successful. Here is an example of what I'm trying to achieve:

var promise = uploadFile();
promise.then(startImageProcessingJob)
.then(saveConvertedImage);

I need help creating a promise that can poll until the data is retrieved or the maximum number of tries is reached.

The author's code uses bluebird. Here is the relevant snippet:

var getJobStatusAsync = Promise.promisifyAll(api);

function poll(jobId, retry) {  
  if(!retry) retry = 5;
  if(!retry--) throw new Error('Too many retries');

  return getJobStatusAsync(jobId)
  .then(function(data) {
    if(data.state === 'error') throw new Error(data.error);
    if(data.state === 'finished') return data;
    return Promise.delay(jobId, 10000).then(poll);
  });

Edit:

In response to Traktor53's comment, I am sharing the logic I have developed so far. My goal is to utilize ZamZar as a third-party service for image conversion within my Angular application. Here is how I plan to use promises:

(1) Upload the file from the client to the server (Node);

(2) Initiate image conversion using the ZamZar API and obtain the JobID;

(3) Poll the ZamZar API for status updates based on the JobID until the image is ready for download. Once the image is ready, retrieve the fileId and download it back to the Node server;

(4) Display the PNG image on the client browser by integrating it into an HTML canvas using three.js and fabric.js.

/* Dependencies */
var express = require('express');
var request = require('request');
var formidable = require('formidable');
var randomstring = require("randomstring");
var fs = require('fs');
var Q = require('q');

/*
 * Node Helper functions
 */
convertFileUtil = function() {

  /**
   * Step 1: Upload file from client to node server.
   * Parses the uploaded file using formidable and moves it to a custom temp directory.
   * Returns a promise with the directory path of the uploaded file for subsequent methods.
   */
  var uploadFileFromClientToNodeServer = function(req) {
    var q = Q.defer();
    var form = new formidable.IncomingForm();
    var tmpFolder = 'upload/' + randomstring.generate() + '/';

    form.parse(req, function(err, fields, files) {
      // File upload handling logic
    });

    return q.promise;
  };

  /**
   * Step 2: Post the temp file to ZamZar for conversion.
   * This method returns a promise containing the JobID of the conversion job.
   */
  var postTempFileToZamZar = function(filePath) {
    // Post file to ZamZar logic
  };

  /*
   * Step 3: Poll for PNG.
   */
  var pollZamZarForOurPngFile = function(dataObj) {
    // Polling logic
  }

  // API
  return {
    uploadFileFromClientToNodeServer: uploadFileFromClientToNodeServer,
    postTempFileToZamZar: postTempFileToZamZar,
    pollZamZarForOurPngFile: pollZamZarForOurPngFile,
  };
};

// Call to convert file.
app.post('/convertFile', function (req, res) {
  var util = convertFileUtil();

  var promise = util.uploadFileFromClientToNodeServer(req);
  promise
  .then(util.postTempFileToZamZar)
  .then(util.pollZamZarForOurPngFile);
  .then(function(data) {
    console.log('Done processing');
  });
});

Answer №1

An interesting design concept to consider:

  1. Develop an onFulfill listener (pollZamZarForOurPngFile) for the promise that resolves with returnObj.
  2. This listener will return a Promise object.
  3. If zambar has finished conversion, the returned promise will be resolved with returnObj (passing it down the chain).
  4. The returned promise will be rejected in case of zamzar errors or too many retries.

Note that fetching the file is left to the next listener (onFulfilled) in the promise chain. I opted for Promise due to its convenience and compatibility with node.js's support for Promise/Aplus standards. Feel free to switch it to Q if you prefer. The polling request code is directly from the zamzar website and may have been inspired by a tutorial example - please verify.

function pollZamZarForOurPngFile( returnObj)
{   var jobID = returnObj.jobId;
    var resolve, reject;
    function unwrap( r, j) { resolve = r, reject = j};
    var promise = new Promise( unwrap);
    var maxRetry = 5;
    var firstDelay = 500;     // 1/2 second
    var retryDelay = 5000;    // 5 seconds

    function checkIfFinished()
    {   // refer to https://developers.zamzar.com/docs under node.js tab for documentation

        request.get ('https://sandbox.zamzar.com/v1/jobs/' + jobID,
        function (err, response, body)
        {   if (err)
            {   reject( new Error("checkIfFinished: unable to get job"));
                return;
            }
            if( JSON.parse(body).status == "successful")
            {   resolve( returnObj);    // fulfill return promise with "returnObj" passed in; 
                return;
            }    
            // has not succeeded, need to retry
            if( maxRetry <= 0)
            {    reject( new Error("checkIfFinished: too many retries"));
            }
            else
            {   --maxRetry;
                setTimeout(checkIfFinished, retryDelay);
            }    
        }
    }).auth(apiKey, '', true);
    setTimeout(checkIfFinished, firstDelay);
    return promise;
}

Answer №2

An updated version of the poll function using only standard Promise/A+ practices

function checkStatus(jobId, attempts) {
    if(!attempts) attempts = 5; // set default number of attempts to 5
    function wait(time) {
        return new Promise(function(resolve) {
            setTimeout(function() {
                resolve();
            }, time);
        });
    }
    function pollForJobStatus() {
        if(!attempts--) throw new Error('Exceeded maximum number of attempts');

        return getAsyncJobStatus(jobId)
        .then(function(data) {
            if (data.status === 'error') throw new Error(data.errorMessage);
            if (data.status === 'completed') return data;
            return wait(10000).then(pollForJobStatus);
        });
    }
    return pollForJobStatus();
};

This code can surely be improved further, but for now, it serves as a solid starting point for the user on a relaxed weekend coding session.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

The event listener function is not functioning properly on images generated by JavaScript

I'm currently working on placing multiple images on a grid in the center of the page and would like to include a function that triggers when each individual image is clicked. The images are dynamically created using JavaScript and inserted into the do ...

Experimenting with a customizable Vue.js autocomplete feature

Check out this sample code: https://jsfiddle.net/JLLMNCHR/09qtwbL6/96/ The following is the HTML code: <div id="app"> <button type="button" v-on:click="displayVal()">Button1</button> <autocomplete v- ...

What is the best way to modify the views directory for deploying on Vercel?

Currently facing an issue trying to deploy my Express application with EJS template on Vercel. Post deployment, encountering an internal server error along with the following message in the logs: Error: Failed to lookup view "home.ejs" in views directory " ...

"Using only JavaScript to target and manipulate child elements within the

I'm currently transitioning from using jQuery to pure JavaScript, and I've just started but am encountering difficulties. Specifically, I am unsure how to select child elements in the DOM. <div class="row-button"> <input type="submi ...

Tips for embedding a file within a text box in HTML and enabling users to make direct edits

I am currently working on a feature that allows users to open a .txt or .html file from their file explorer and paste the contents into a textarea for editing and saving purposes. My question is whether it's possible to read the file content and auto ...

Is there a way to align the image next to the form and ensure they are both the same size?

I've been struggling to resize the image to match the form size, but I can't seem to get it right. Can anyone provide me with some guidance on this issue? I have obtained this contact form from a website that I plan to modify slightly, but I need ...

FusionMaps XT integration with VueJs: Troubleshooting connectorClick events issue

I am experiencing some issues with events connectorClick on my VueJS processed map. When I click on the connector line in the example below, the alert function does not seem to work as expected. Vue.use(VueFusionCharts, FusionCharts); let grphMap = new ...

Receiving a 401 error while attempting to make an axios get request with authentication headers

I have been utilizing axios in my React project to fetch data from MongoDB. However, I am facing a challenge with the axios get requests returning a 401 error when I include my auth middleware as a parameter. This middleware mandates that the user must pos ...

Nested Promises in Angular's $q Library

I'm facing an issue with a function that should return a list of favorite locations. Here's the code snippet in question: When I call LocationsFactory.getFavoriteLocations(), it returns a promise like this: getFavoriteLocations: function() { ...

Vanish and reappear: Javascript block that disappears when clicked and shows up somewhere else

My goal is to make three squares randomly disappear when clicked on the page. However, I am facing an issue where some of them, usually the 2nd or 3rd one, reappear elsewhere on the page after being clicked. I have created a jsfiddle to demonstrate this: ...

Warning displayed on form input still allows submission

How can I prevent users from inserting certain words in a form on my website? Even though my code detects these words and displays a popup message, the form still submits the data once the user acknowledges the message. The strange behavior has me puzzled. ...

Tips for crafting interactive Dropdown menus

Visit this link for more information Hello all, I am a beginner in HTML and Javascript and seeking guidance on how to create dynamic dropdown menus similar to the ones on the provided website. I have successfully implemented the source code, but my questi ...

Retrieving a result from a function call is a fundamental aspect of programming

I have a scenario where I am initiating a call from a controller within AngularJS. This call passes some data to a service in order to receive a response that needs to be conditionally checked. Controller patents.forEach(function(item){ // The "patents" ...

Positioning the comments box on Facebook platform allows users to

Need assistance, I recently integrated the Facebook comments box into my Arabic website, but I am facing an issue where the position of the box keeps moving to the left. Here is an example of my website: Could someone please suggest a solution to fix the ...

What is the best way to reveal and automatically scroll to a hidden div located at the bottom of a webpage?

Whenever the image is clicked, I want to display a hidden div with effects like automatically scrolling down to the bottom This is what my code looks like: $(document).ready(function() { $('#test').click(function() { $(&apos ...

Navigate to the editing page with Thymeleaf in the spring framework, where the model attribute is passed

My goal is to redirect the request to the edit page if the server response status is failed. The updated code below provides more clarity with changed variable names and IDs for security reasons. Controller: @Controller @RequestMapping("abc") public clas ...

Is it possible to install the lib ldap-client module in node.js?

I'm having trouble installing the lib ldap-client package in Node.js. To try and solve this issue, I consulted the following page: https://github.com/nodejs/node-gyp. In an attempt to fix the problem, I have installed python, node-gyp, and Visual St ...

What is causing the Firebase emulator to crash when I run my Express code?

This project is utilizing express.js along with firebase. Whenever attempting to access a different file containing routes, it results in failure. Instead of successful emulation, an error is thrown when running this code: const functions = require(" ...

AngularJS efficiently preloading json file

I am just starting to learn about angularJS. Apologies if my question is not very clear. Here is the problem I am facing: I have a JSON file that is around 20KB in size. When I attempt to load this file using the 'factory' method, I am receivin ...

What is the process for generating an alert box with protractor?

While conducting tests, I am attempting to trigger an alert pop-up box when transitioning my environment from testing to production while running scripts in Protractor. Can someone assist me with this? ...