单个函数中的多个 Promise 链
Multiple Promise Chains in Single Function
我有一些代码可以根据我通过对服务器的 AJAX 请求检索的场景动态生成 AJAX 请求。
想法是:
- 服务器提供了一个"Scenario"让我生成一个AJAX请求。
- 我根据场景生成 AJAX 请求。
- 然后我在循环中一遍又一遍地重复这个过程。
我在这里承诺: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" 函数中。
我不知道如何 运行 多个承诺链。我在 "deferred" 变量中有一组承诺,但链只在第一个继续——尽管在 for 循环中。
谁能解释这是为什么?您可以在此处查看发生的位置:http://jsfiddle.net/3Lddzp9j/10/
我将尝试使用 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();
好的,让我解释一下上面的解决方案:
- 首先假设
getScenario
为您提供初始的 json(实际上 returns 一个由 json 解决的承诺)
- 创建每个指令的结构
- promisify每条指令,这样每条指令实际上都是一个promise
分辨率值将是
ajaxRequest
返回的承诺
ajaxRequest
returns一个promise,其解析值是请求的结果,这也意味着createPromisifiedInstruction
解析值将是ajaxRequest
[的解析值=38=]
- Return 与
Q.all
的单个承诺,它实际做的是在解决所有与它一起构建的承诺时实现自己:),如果其中一个失败并且您确实需要无论如何都要解决承诺使用Q.allSettled
- 用前面所有承诺的分辨率值做任何你想做的事,注意
instructionResults
是一个数组,按照声明的顺序保存每个承诺的分辨率值
您的 waitForTimeout
函数的循环中有一个 return
语句。这意味着函数将在循环的第一次迭代后进入 return,这就是你出错的地方。
您还使用了延迟反模式,并在不需要的地方使用了承诺。您不需要 return 来自 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();
尝试在 setTimeout
和 Number(settings.frequency) * (1 + key)
内使用 deferred.notify
作为 setTimeout
持续时间; msg
在 deferred.notify
记录到 console
在 deferred.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>
jsfiddle http://jsfiddle.net/3Lddzp9j/13/
主要问题是:
waitForTimeout
没有传递所有指令
- 即使
waitForTimeout
已修复,也不会编写 callApi
来执行多个 ajax 调用。
代码还有许多其他问题。
- 您确实需要进行一些数据检查(以及相关的错误处理)以确保
data
中存在预期的组件。
mapToInstruction
是不必要的步骤 - 您可以直接从 data
映射到 ajax 选项 - 不需要中间数据转换。
waitForTimeout
可以大大简化为单个承诺,通过单个超时解决。
- 承诺链中的同步函数不需要 return 承诺 - 它们可以 return 结果或未定义。
一直坚持 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();
这将在出现错误时停止,但可以轻松调整以继续。
我有一些代码可以根据我通过对服务器的 AJAX 请求检索的场景动态生成 AJAX 请求。
想法是:
- 服务器提供了一个"Scenario"让我生成一个AJAX请求。
- 我根据场景生成 AJAX 请求。
- 然后我在循环中一遍又一遍地重复这个过程。
我在这里承诺: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" 函数中。
我不知道如何 运行 多个承诺链。我在 "deferred" 变量中有一组承诺,但链只在第一个继续——尽管在 for 循环中。
谁能解释这是为什么?您可以在此处查看发生的位置:http://jsfiddle.net/3Lddzp9j/10/
我将尝试使用 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();
好的,让我解释一下上面的解决方案:
- 首先假设
getScenario
为您提供初始的 json(实际上 returns 一个由 json 解决的承诺) - 创建每个指令的结构
- promisify每条指令,这样每条指令实际上都是一个promise
分辨率值将是
ajaxRequest
返回的承诺
ajaxRequest
returns一个promise,其解析值是请求的结果,这也意味着createPromisifiedInstruction
解析值将是ajaxRequest
[的解析值=38=]- Return 与
Q.all
的单个承诺,它实际做的是在解决所有与它一起构建的承诺时实现自己:),如果其中一个失败并且您确实需要无论如何都要解决承诺使用Q.allSettled
- 用前面所有承诺的分辨率值做任何你想做的事,注意
instructionResults
是一个数组,按照声明的顺序保存每个承诺的分辨率值
您的 waitForTimeout
函数的循环中有一个 return
语句。这意味着函数将在循环的第一次迭代后进入 return,这就是你出错的地方。
您还使用了延迟反模式,并在不需要的地方使用了承诺。您不需要 return 来自 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();
尝试在 setTimeout
和 Number(settings.frequency) * (1 + key)
内使用 deferred.notify
作为 setTimeout
持续时间; msg
在 deferred.notify
记录到 console
在 deferred.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>
jsfiddle http://jsfiddle.net/3Lddzp9j/13/
主要问题是:
waitForTimeout
没有传递所有指令- 即使
waitForTimeout
已修复,也不会编写callApi
来执行多个 ajax 调用。
代码还有许多其他问题。
- 您确实需要进行一些数据检查(以及相关的错误处理)以确保
data
中存在预期的组件。 mapToInstruction
是不必要的步骤 - 您可以直接从data
映射到 ajax 选项 - 不需要中间数据转换。waitForTimeout
可以大大简化为单个承诺,通过单个超时解决。- 承诺链中的同步函数不需要 return 承诺 - 它们可以 return 结果或未定义。
一直坚持 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();
这将在出现错误时停止,但可以轻松调整以继续。