单函数中的多个Promise链

时间:2015-03-03 00:23:22

标签: javascript jquery ajax jquery-deferred

我有一些代码可以根据我通过AJAX请求检索到服务器的场景动态生成AJAX请求。

这个想法是:

  1. 服务器提供"场景"让我生成一个AJAX请求。
  2. 我根据场景生成一个AJAX请求。
  3. 然后我在循环中反复重复这个过程。
  4. 我在这里做承诺:http://jsfiddle.net/3Lddzp9j/11/

    但是,我尝试编辑上面的代码,以便从最初的AJAX请求中处理一系列场景。

    IE:

    {
    "base": {
        "frequency": "5000"
    },
    "endpoints": [
        {
            "method": "GET",
            "type": "JSON",
            "endPoint": "https://api.github.com/users/alvarengarichard",
            "queryParams": {
                "objectives": "objective1, objective2, objective3"
            }
        },
        {
            "method": "GET",
            "type": "JSON",
            "endPoint": "https://api.github.com/users/dkang",
            "queryParams": {
                "objectives": "objective1, objective2, objective3"
            }
        }
    ]
    

    这似乎是直截了当的,但问题似乎出现在" waitForTimeout"功能

    我无法弄清楚如何运行多个承诺链。我有一系列承诺在"延期"变量,但链只在第一个链上继续 - 尽管是在for循环中。

    任何人都可以提供有关原因的见解吗?你可以在这里看到这里发生的地方:http://jsfiddle.net/3Lddzp9j/10/

4 个答案:

答案 0 :(得分:1)

我会尝试使用KrisKowal的q来回答你的问题,因为我对jQuery生成的承诺并不十分熟练。

首先,我不确定你是否想要串联或并行解决承诺数组,在提出的解决方案中,我并行地解决了所有这些问题:),在系列中解决它们我会使用Q的reduce

function getScenario() { ... }

function ajaxRequest(instruction) { ... }

function createPromisifiedInstruction(instruction) {
  // delay with frequency, not sure why you want to do this :(
  return Q.delay(instruction.frequency)
    .then(function () {
      return this.ajaxRequest(instruction);
    });
}

function run() {
  getScenario()
    .then(function (data) {
      var promises = [];
      var instruction;
      var i;
      for (i = 0; i < data.endpoints.length; i += 1) {
        instruction = {
          method: data.endpoints[i].method,
          type: data.endpoints[i].type,
          endpoint: data.endpoints[i].endPoint,
          frequency: data.base.frequency
        };
        promises.push(createPromisifiedInstruction(instruction));   
      }
      // alternative Q.allSettled if all the promises don't need to
      // be fulfilled (some of them might be rejected)
      return Q.all(promises);
    })
    .then(function (instructionsResults) {
      // instructions results is an array with the result of each
      // promisified instruction
    })
    .then(run)
    .done();
}

run();

好的,让我解释一下上面的解决方案:

  1. 首先假设getScenario为您提供了您开始使用的初始json(实际上返回了使用json解析的promise)
  2. 创建每条指令的结构
  3. 宣传每条指令,以便每一条指令实际上都是一条承诺 分辨率值将是ajaxRequest
  4. 返回的承诺
  5. ajaxRequest会返回一个承诺,其分辨率值是请求的结果,这也意味着createPromisifiedInstruction分辨率值将是ajaxRequest的分辨率值
  6. 使用Q.all返回一个单一的承诺,当它构建的所有承诺都已解决时,它实际上做的是履行自己:),如果其中一个失败并且您实际上需要解决承诺仍然使用{ {1}}
  7. 使用所有先前承诺的分辨率值执行任何操作,请注意Q.allSettled是一个数组,按照声明的顺序保存每个承诺的分辨率值
  8. 参考:KrisKowal's Q

答案 1 :(得分:1)

return函数循环中有waitForTimeout个语句。这意味着该函数将在循环的第一次迭代后返回,这就是您出错的地方。

您还使用延迟反模式,并在不需要它们的地方使用promises。除非有等待的东西,否则您无需从then处理程序返回承诺。

关键是您需要将指令的每个映射到承诺。 Array#map非常适合这一点。请使用正确的承诺库,not jQuery promises编辑,但如果您绝对必须使用jQuery承诺......):

var App = (function ($) {
    // Gets the scenario from the API
    // NOTE: this returns a promise
    var getScenario = function () {
        console.log('Getting scenario ...');
        return $.get('http://demo3858327.mockable.io/scenario');
    };

    // mapToInstructions is basically unnecessary. each instruction does
    // not need its own timeout if they're all the same value, and you're not
    // reshaping the original values in any significant way

    // This wraps the setTimeout into a promise, again
    // so we can chain it
    var waitForTimeout = function(data) {
        var d = $.Deferred();
        setTimeout(function () {
            d.resolve(data.endpoints);
        }, data.base.frequency);
        return d.promise();
    };

    var callApi = function(instruction) {
        return $.ajax({
            type: instruction.method,
            dataType: instruction.type,
            url: instruction.endPoint
        });
    };

    // Final step: call the API from the 
    // provided instructions
    var callApis = function(instructions) {
        console.log(instructions);
        console.log('Calling API with given instructions ...');
        return $.when.apply($, instructions.map(callApi));
    };

    var handleResults = function() {
        var data = Array.prototype.slice(arguments);
        console.log("Handling data ...");
    };

    // The 'run' method
    var run = function() {
        getScenario()
        .then(waitForTimeout)
        .then(callApis)
        .then(handleResults)
        .then(run);
    };

    return {
        run : run
    }
})($);

App.run();

答案 2 :(得分:1)

尝试在deferred.notifysetTimeout内使用Number(settings.frequency) * (1 + key)作为setTimeout期限; msg deferred.notify consoledeferred.progress回调时登录.then,超时后 var App = (function ($) { var getScenario = function () { console.log("Getting scenario ..."); return $.get("http://demo3858327.mockable.io/scenario2"); }; var mapToInstruction = function (data) { var res = $.map(data.endpoints, function(settings, key) { return { method:settings.method, type:settings.type, endpoint:settings.endPoint, frequency:data.base.frequency } }); console.log("Instructions recieved:", res); return res }; var waitForTimeout = function(instruction) { var res = $.when.apply(instruction, $.map(instruction, function(settings, key) { return new $.Deferred(function(dfd) { setTimeout(function() { dfd.notify("Waiting for " + settings.frequency + " ms") .resolve(settings); }, Number(settings.frequency) * (1 + key)); }).promise() }) ) .then(function() { return this }, function(err) { console.log("error", err) } , function(msg) { console.log("\r\n" + msg + "\r\nat " + $.now() + "\r\n") }); return res }; var callApi = function(instruction) { console.log("Calling API with given instructions ..." , instruction); var res = $.when.apply(instruction, $.map(instruction, function(request, key) { return request.then(function(settings) { return $.ajax({ type: settings.method, dataType: settings.type, url: settings.endpoint }); }) }) ) .then(function(data) { return $.map(arguments, function(response, key) { return response[0] }) }) return res }; var handleResults = function(data) { console.log("Handling data ..." , JSON.stringify(data, null, 4)); return data }; var run = function() { getScenario() .then(mapToInstruction) .then(waitForTimeout) .then(callApi) .then(handleResults) .then(run); }; return { // This will expose only the run method // but will keep all other functions private run : run } })($); // ... And start the app App.run();内的第三个函数参数

<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js">
</script>
{{1}}

jsfiddle http://jsfiddle.net/3Lddzp9j/13/

答案 3 :(得分:1)

主要问题是:

  • waitForTimeout未传递所有说明
  • 即使修复了waitForTimeout,也不会编写callApi来执行多个ajax调用。

代码还存在许多其他问题。

  • 您确实需要一些数据检查(以及相关的错误处理),以确保data中存在预期的组件。
  • mapToInstruction是一个不必要的步骤 - 您可以直接从data映射到ajax选项 - 无需进行中间数据转换。
  • waitForTimeout可以大大简化为单个承诺,通过一次超时解决。
  • promise链中的同步函数不需要返回promise - 它们可以返回结果或未定义。

坚持使用jQuery,你应该得到这样的结果:

var App = (function ($) {
    // Gets the scenario from the API
    // sugar for $.ajax with GET as method - NOTE: this returns a promise
    var getScenario = function () {
        console.log('Getting scenario ...');
        return $.get('http://demo3858327.mockable.io/scenario2');
    };

    var checkData = function (data) {
        if(!data.endpoints || !data.endpoints.length) {
            return $.Deferred().reject('no endpoints').promise();
        }
        data.base = data.base || {};
        data.base.frequency = data.base.frequency || 1000;//default value
    };

    var waitForTimeout = function(data) {
        return $.Deferred(function(dfrd) {
            setTimeout(function() {
                dfrd.resolve(data.endpoints);
            }, data.base.frequency);
        }).promise();
    };

    var callApi = function(endpoints) {
        console.log('Calling API with given instructions ...');
        return $.when.apply(null, endpoints.map(ep) {
            return $.ajax({
                type: ep.method,
                dataType: ep.type,
                url: ep.endpoint
            }).then(null, function(jqXHR, textStatus, errorThrown) {
                return textStatus;
            });
        }).then(function() {
            //convert arguments to an array of results
            return $.map(arguments, function(arg) {
                return arg[0];
            });
        });
    };

    var handleResults = function(results) {
        // results is an array of data values/objects returned by the ajax calls.
        console.log("Handling data ...");
        ...
    };

    // The 'run' method
    var run = function() {
        getScenario()
        .then(checkData)
        .then(waitForTimeout)
        .then(callApi)
        .then(handleResults)
        .then(null, function(reason) {
            console.error(reason);
        })
        .then(run);
    };

    return {
        run : run
    }
})(jQuery);

App.run();

这将在出错时停止,但可以很容易地进行调整以继续。