我不知道如何恰当地写这个问题的标题,但我会解释我的问题:
假设有一个包含一些数据的列表,例如:["first", "second", "third"]
还有一个AJAX调用对它的参数做一些事情,例如:
function ajax(data){
return new Promise(function (resolve, reject) {
setTimeout(() => resolve(data+"1"), 2000)
});
}
对于每个AJAX调用,您需要一个后续操作,例如:
ajax(e).done(data => d.resolve(data+"2"));
现在我想让AJAX调用和后续动作异步的每个项目的列表,但要等待(非阻塞),直到每个项目完成。
对于一个解决方案,我想使用生成器和co库。
只有对每个列表项异步运行AJAX调用才有效:
var consoleLine = "<p class="console-line"></p>";
console = {
log: function (text) {
$("#console-log").append($(consoleLine).html(text));
}
};
co(function*(){
let res = yield ["first", "second", "third"].map(e => ajax(e));
res.forEach((a, b, c) => console.log(a));
});
function ajax(data){
return new Promise(function (resolve, reject) {
setTimeout(() => resolve(data+"1"), 2000)
});
}
.console-line
{
font-family: monospace;
margin: 2px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://raw.githubusercontent.com/tj/co/master/index.js"></script>
<div id="console-log"></div>
但是与后续操作一起运行它不起作用:
var consoleLine = "<p class="console-line"></p>";
console = {
log: function (text) {
$("#console-log").append($(consoleLine).html(text));
}
};
co(function*(){
let res = yield test(["first", "second", "third"]);
res.forEach((a, b, c) => console.log(a));
});
function test(g) {
return g.map(e => function(){
let d = new $.Deferred();
ajax(e).done(data => d.resolve(data+"2"));
return d.promise();
});
}
function ajax(data){
return new Promise(function (resolve, reject) {
setTimeout(() => resolve(data+"1"), 2000)
});
}
.console-line
{
font-family: monospace;
margin: 2px;
}
<script src="https://raw.githubusercontent.com/tj/co/master/index.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="console-log"></div>
为什么?如何使我的要求生效?
你的问题是:
return g.map(e => function(){
// ^^ ^^^^^^^^^^
let d = new $.Deferred();
ajax(e).done(data => d.resolve(data+"2"));
return d.promise();
});
这是一个返回函数表达式的箭头函数。你要么想要
return g.map(function(e) {
或
return g.map(e => {
让它工作,否则你只会得到一个函数数组(co
会以奇怪的方式对待它)。
是的,你绝对应该使用then
而不是done
+延迟。
现在我想让AJAX调用和后续动作异步的每个项目的列表,但希望等待(非阻塞),直到每个项目完成。
听起来你想等所有的承诺都实现。这就是Promise.all()和$。
var data = ["first", "second", "third"];
function ajax(data){
let d = new $.Deferred();
setTimeout(() => d.resolve(data+"1"), 1000);
return d.promise();
}
function followUpAction(data){
console.log('following', data);
}
var promises = data.map(function(item) {
return ajax(item).then(followUpAction);
});
$.when.apply($, promises).then(function() {
console.log('all done');
});
我不喜欢使用带有承诺的生成器。当ES7的async
await
出现时,我会重新考虑这个问题。我相信这项工作可以很好地通过利用Array.prototype.reduce()
的功能方式完成,如果这些依赖于它们在数组中的顺序,并且需要一个接一个地运行。否则,我建议使用Array.prototype.map()
和Promise.all
的组合。
假设使用错误优先回调。
function promisify(fun){
return (data) => new Promise((resolve,reject) => fun(data, (err,res) => err ? reject(err) : resolve(res)));
}
function async(data, callback){
data.val+= " msec" // do something with the data asynchronously
setTimeout(_ => callback(false,data.val),data.dur); // we are expected to handle returned data in our callback
}
function myCallback(result){ // supposed to work in the asych function but now moved to then stage
console.log(result);
return result;
}
var dataSet = [100, 200, 300],
promisifiedAsych = promisify(async),
chainedPromises = dataSet.reduce((prom,data) => prom.then(_ => promisifiedAsych({val:data, dur:data*10}))
.then(myCallback), Promise.resolve());
chainedPromises.then(val => console.log("dataSet completed and returned", val))
我想使用生成器,因此rodneyrehm的答案不能解决我的需求。但他的回答确实帮我解决了问题!
而不是由我自己创建和返回一个新的承诺,我现在只是使用.then()
本身确实返回一个承诺(自jQuery 1.8)。这就是我所需要做的改变,以使它工作:
老:
function test(g) {
return g.map(e => function(){
let d = new $.Deferred();
ajax(e).done(data => d.resolve(data+"2"));
return d.promise();
});
}
新:function test(g) {
return g.map(e => ajax(e).then(data => data+"2"));
}
最终的解决方案看起来是这样的:
var consoleLine = "<p class="console-line"></p>";
console = {
log: function (text) {
$("#console-log").append($(consoleLine).html(text));
}
};
var list = ["first", "second", "third"];
co(function*(){
let res = yield list.map(e => ajax(e).then(r => r+"2"));
res.forEach((a, b, c) => console.log(a));
});
function ajax(data){
return new Promise(function (resolve, reject) {
setTimeout(() => resolve(data+"1"), 2000)
});
}
.console-line
{
font-family: monospace;
margin: 2px;
}
<script src="https://raw.githubusercontent.com/tj/co/master/index.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="console-log"></div>
谢谢罗德尼雷姆给我指出解决办法。