用生成器异步调用列表的每一项

Asynchronous call for every item of list with generator

本文关键字:一项 列表 异步 调用      更新时间:2023-09-26

我不知道如何恰当地写这个问题的标题,但我会解释我的问题:

假设有一个包含一些数据的列表,例如:["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>

谢谢罗德尼雷姆给我指出解决办法。