使用Q轮询承诺

时间:2016-01-09 02:16:53

标签: javascript promise q

我有类似于此博客文章中所述的情况:Polling with promises。作者描述了在返回JobID之前如何使用promises进行轮询。我想使用Q转换它。

我很乐意将代码作为起点发布,但我不确定要发布什么。理想情况下,我试图将承诺链接在一起。我一直在试验Q.delay(),但它似乎没有实现我的目标。

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

是否可以提供有关如何创建承诺的建议,该承诺将继续轮询,直到检索到数据(或满足最大尝试次数)。

以下是使用bluebird的作者代码。

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);
  });

修改

在回应Traktor53的评论时,我正在添加到目前为止的逻辑。我试图避免添加导致问题膨胀的额外代码。

目标:

在我的Angular应用程序中,我想使用ZamZar第三方服务将图像转换为PNG格式。我的设计实现是使用promises:

(1)将文件从客户端上传到服务器(Node);

(2)使用ZamZar API开始图像转换(获取JobID);

(3)使用JobID,轮询ZamZar API进行状态更新,直到映像准备下载。图像准备就绪后,我可以获取fileId并将文件下载回节点服务器。

(4)一旦PNG图像重新出现在我的服务器上,我想将图像返回到客户端浏览器并放入HTML画布(使用three.js和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');

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

  /**
   * Step 1: upload file from client to node server.
   * formidable is used for file upload management. This means the file is
   * automatically uploaded to a temp directory. We are going to move the
   * uploaded file to our own temp directory for processing.
   * Return Type: A Promise is returned with payload containing the directory
   * path for the image file. This path is referenced in subsequent chained methods.
   */
  var uploadFileFromClientToNodeServer = function(req) {
    var q = Q.defer();
    var form = new formidable.IncomingForm();
    var tmpFolder = 'upload/' + randomstring.generate() + '/';

    //Use formidable to parse the file upload.
    form.parse(req, function(err, fields, files) {
      if (err) {
        console.log(err);
        throw err;
      }

      //When upload is successful, create a temp directory and MOVE file there.
      //Again, file is already uploaded. There is no need to use fs.writeFile* methods.
      mkdirp(tmpFolder, function (err) {
        if (err) {
          q.reject(err);
        } else {

          //File will be saved here.
          var tmpFileSavedLocation = tmpFolder + files.file.name;

          //Call fs.rename to MOVE file from formidable temp directory to our temp directory.
          fs.rename(files.file.path, tmpFileSavedLocation, function (err) {
            if (err) {
              q.reject(err);
            }
            console.log('File saved to directory:', tmpFileSavedLocation);
            q.resolve(tmpFileSavedLocation);
          });
        }
      });
    });

    return q.promise;
  };

  /**
   * Step 2: Post the temp file to zam zar. ZamZar is an API service that converts
   * images to a different file format. For example, when a user uploads an Adobe
   * Illustrator EPS file; the file is sent to zamzar for conversion to a PNG. all
   * image formats are returned as PNG which will be added to the canvas.
   * Return: This promise will return the JobID of our submission. The JobID will be
   * used in subsequent promise to retrieve the converted image.
   */
  var postTempFileToZamZar = function(filePath) {
    console.log('FilePath', filePath);
    var q = Q.defer();
    var formData = {
      target_format: 'png',
      source_file: fs.createReadStream(filePath),
    };
    //console.log('OK', formData);

    //Send file to zamzar for conversion.
    request.post({ url: 'https://sandbox.zamzar.com/v1/jobs/', formData: formData }, function (err, response, body) {
      if (err) {
        console.log('An error occurred', err);
        q.reject(err);
      } else {
        var jsonData = JSON.parse(body);
        console.log('SUCCESS! Conversion job started:', jsonData.id);

        //This object will be returned in promise payload.
        var returnObj = {
          filePath: filePath,
          jobId: jsonData.id,
        };

        console.log('Process complete. Returning: ', returnObj);
        q.resolve(returnObj);

        return q.promise;
      }

    }).auth(zamzarApiKey, '', true);
  };

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

    console.log('pollZamZarForOurPngFile', dataObj);
  }

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

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

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

2 个答案:

答案 0 :(得分:2)

使用标准Promise / A +

poll函数的工作版本
function poll(jobId, retry) {
    if(!retry) retry = 5; // default retries = 5
    function delay(timeout) {
        return new Promise(function(fulfill) {
            setTimeout(function() {
                fulfill();
            }, timeout);
        });
    }
    function poller() {
        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 delay(10000).then(poller);
        });
    }
    return poller();
};

我觉得这段代码可能写得太远了#34;更好" ......但是星期六,所以这是"周末代码",它至少为OP提供了更好的跳跃点

答案 1 :(得分:2)

可能感兴趣的设计理念:

  1. 为履行pollZamZarForOurPngFile的承诺编写一个onFulfill监听器(returnObj)。
  2. 此侦听器返回Promise对象。
  3. 如果zambar完成转换,则返回的承诺将通过returnObj(将其传递给链)来实现。
  4. 如果发生zamzar错误或重试次数过多,则会拒绝返回的承诺。
  5. 请注意,这会将文件检索到promise链中的下一个(onFulfilled)侦听器。我使用Promise是为了方便,因为node.js支持它并且它的Promise / Aplus兼容。根据需要将其转换为Q. 轮询请求代码是直接关闭zamzar网站,可能来自教程示例 - 请检查它。

    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 second?
    
        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;
    }