简体   繁体   中英

How do you map generator results concurrently?

How can you map the results of async calls in a generator, concurrently?

var generator = (function *() {
  var lotsOfThings = yield asyncTask.call(generator);

  var mappedResults = yield lotsOfThings.map(thing => {
    // fails with a syntax error unless you do a `for…of` loop, but also doesn’t run concurrently regardless
    return (yield asyncTask.call(generator, thing));
  });

  // then do something with mappedResults
})();

generator.next();

function asyncTask(…query) {
  somethingAsync(…query, (err, res) => this.next(res));
}

Also, even in a regular for...of loop, you can't run each asyncTask concurrently. yield will cause a pause between each task, essentially making a synchronous AJAX request. Ideally you'd want it to work like it does with promises, like this paradigm:

// these tasks will run concurrently (unlike the above example)
let promises = someThings.map(thing => {
  return new Promise((resolve, reject) => {
    somethingAsync((err, res) => {
      resolve(res);
    });
  });
});

Promise.all(promises).then(/* do stuff */);

The promise approach can get hairy 'cause of all the nesting, but the benefit is that the async tasks can run concurrently… whereas the generators look nice, but looping through tasks is not concurrent. Any ideas?

I tried to sketch something similar without third-party libraries:

// Async runner
function async(generator){
  var process = function(result){       
    if (result.done) {
      return;
    }
    (Array.isArray(result.value) ? Promise.all(result.value) : result.value).then(function(value){
      process(sequence.next(value));
    });
  };

  var sequence = generator();
  var next = sequence.next();
  process(next);
};

// Generator function
var generator = function* () {
  var list = yield getList();
  console.log(list); // outputs [1, 2, 3, 4, 5]

  var details = yield list.map(p => getDetails(p));    
  console.log(details); // outputs [11, 12, 13, 14, 15]
}

// Your async requests go here
function fakeAsync(f) {
  return new Promise(function(resolve, reject) {
    setTimeout(function() {
      f(resolve);
    }, 500);
  });
}

function getList() {
  return fakeAsync(function(resolve) {
    resolve([1, 2, 3, 4, 5]);                  
  });
}

function getDetails(i) {
  return fakeAsync(function(resolve) {
    resolve(i + 10);                  
  });
}

async(generator);

Is it what you were trying to achieve?

The answer I was looking for can be implemented with co .

co(function* () {
  // maybe we need a token from our API before we can do anything
  let token = yield new Promise((resolve, reject) {
    getToken(token => resolve(token));
  });

  // these run in parallel
  var queries = yield [
    request('/route', token),
    request('/another-route', token)
  ];

  // [[], []] becomes [...]
  return _.flatten(queries);
});

// our async request function returns a promise
function request(route, token) {
  return new Promise((resolve, reject) => {
    somethingAsync(route, token, (res) => {
      resolve(res);
    });
  });
}

edit : changed somethingAsync to not be then able, as in my actual case, it's a call to a 3rd party API that does not already return a promise.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM