【问题标题】:Polling with promises using Q使用 Q 轮询 Promise
【发布时间】:2016-01-10 08:20:32
【问题描述】:

我的情况与这篇博文中描述的类似:Polling with promises。作者描述了在返回 JobID 之前如何使用 promise 进行轮询。我想使用Q 转换它。

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

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

您能否提供有关如何创建将继续轮询直到检索到数据(或达到最大尝试次数)的 Promise 的建议。

这是作者使用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 格式。我的设计实现是使用 Promise 来:

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

(2)使用ZamZar API启动图片转换(获取JobID);

(3) 使用 JobID,轮询 ZamZar API 以获取状态更新,直到图像可供下载。图像准备好后,我可以获取 fileId 并将文件下载回 Node 服务器。

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

【问题讨论】:

  • Here is the author's code 代码有缺陷,所以不要将其用作起点
  • 谢谢@Jaromanda,有什么特别突出的缺陷吗?
  • 1 - 重试永远不会反馈到投票中,因此retry 将始终未定义,并且投票的第一行将始终将其设置为 5,因此这将永远重试。 2 - 即使重试被反馈到 poll 中,poll 中的第一行会在达到 0 时将其重置为 5,因此下一行永远不会为真
  • 术语不清楚。在引用的poll 函数中,没有说明导致延迟调用的data.state 的字符串值。你能告诉我们它可能是什么吗?您如何设想startImageProcessingJob 目标函数会遇到这种状态。
  • @Traktor53,我已经用额外的 cmets 更新了这个问题。我不确定 data.state 的价值是什么。 ZamZar API 将返回 JSON 字符串作为响应。根据 API 调用,响应将包含 JobId(用于处理文件转换)或 FileId(当文件准备好下载时)。

标签: javascript promise q


【解决方案1】:

可能感兴趣的设计理念:

  1. 为通过returnObj 实现的promise 编写一个onFulfill 监听器(pollZamZarForOurPngFile)。
  2. 此侦听器返回一个 Promise 对象。
  3. 如果 zambar 已完成转换,则返回的 Promise 将通过 returnObj 实现(沿链向下传递)。
  4. 如果发生 zamzar 错误或重试次数过多,则返回的 Promise 将被拒绝。

请注意,这会将文件检索到 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;
}

【讨论】:

  • 感谢您的帮助。有用。忽略 API 中的示例,我也觉得自己像个傻瓜。不知道我在想什么。
【解决方案2】:

poll 函数的工作版本,仅使用标准 Promise/A+

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

我觉得这段代码可能会写得“更好”......但现在是星期六,所以这是“周末代码”,它至少为 OP 提供了一个更好的起点

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-08-10
    • 2014-06-21
    • 1970-01-01
    • 1970-01-01
    • 2015-01-19
    • 2013-09-26
    相关资源
    最近更新 更多