функция с обратным вызовом в цикле foreach - PullRequest
2 голосов
/ 09 марта 2019

Я пытаюсь использовать функцию с обратным вызовом в цикле forEach.

Мне нужно дождаться завершения выполнения, прежде чем перейти к следующему шагу.

Вот мой код:

const arr = '6,7,7,8,8,5,3,5,1'

const id = arr.split(',');

const length = id.length;


id.forEach( (x, index) => {
  (function FnWithCallback () {
    setTimeout(() => { console.log(x) }, 5000);
  })();
});

console.log('done');

Я придумал взлом:

const arr = '6,7,7,8,8,5,3,5,1'

const id = arr.split(',');

const length = id.length;

const fn = () => {
  return new Promise (resolve => {
    id.forEach( (id, index) => {
      setTimeout(() => {console.log(id)}, 3000);

      if(index === (length - 1))
         resolve();
    })
  })
}

fn().then(()=> {
  console.log('done');
})

Но взлом, похоже, сломан.

Могу ли я найти реальное решение для этого?Пакет NPM был бы действительно полезен.

Примечание: я взглянул на async.js.Я не уверен, что это то, чего я хочу, так как я пытаюсь избежать ада обратного вызова.

Ответы [ 4 ]

5 голосов
/ 09 марта 2019

Решение состоит в том, чтобы обещать функцию обратного вызова и затем использовать Array.prototype.map() в сочетании с Promise.all():

const arr = '6,7,7,8,8,5,3,5,1'

function FnWithCallback (id, cb) {
  setTimeout(cb, 1000, id)
}

const promisified = id => new Promise(resolve => {
  FnWithCallback(id, resolve)
})

const promises = arr.split(',').map(promisified)

Promise.all(promises).then(id => {
  console.log(id)
  console.log('Done')
})

Если ваш API обратного вызова следует соглашению Node.js (error, result) => ..., то вы должны использовать util.promisify() для обещания функции или проверить документацию, чтобы увидеть, не приведет ли пропуск аргумента обратного вызова к call, чтобы вернуть обещание, так как многие пакеты предоставляют готовые API на основе обещаний прямо сейчас.

4 голосов
/ 09 марта 2019

Если вы хотите убедиться, что асинхронные действия выполняются не параллельно, а одно за другим, читайте дальше.Если вы хотите получить результаты по порядку, но не хотите, чтобы они работали параллельно, см. ответ Патрика .

. Вы можете использовать функцию async, чтобы иметь возможность await обещания в цикле for, для которых обещанный таймер действительно полезен:

const timer = ms => new Promise(res => setTimeout(res, ms));

(async function() {
  for(const id of ["6", "7", "7" /*...*/]) {
     await timer(5000);
     console.log(id);
  }
  console.log("done");
})();

Этого также можно добиться с помощью цепочки обратных вызовов, однако я не уверен, что это понятно / полезно (просто хотел показать, что обратные вызовы не должны быть из ада):

["6", "7", "7" /*..*/].reduceRight(
  (next, id) => () => setTimeout(() => {
     console.log(id);
     next();
  }, 5000),
  () => console.log("done")
)();
1 голос
/ 09 марта 2019

Вы можете связать обещания, используя Array.prototype.reduce(), начиная с начального разрешенного обещания.Вам придется превратить ваш обратный вызов в обещание, чтобы вы могли связать их:

const arr = '6,7,7,8,8,5,3,5,1'
const ids = arr.split(',');
const length = ids.length;

const timeout = (fn, ms) => new Promise(res => {
  setTimeout(() => { fn(); res(); }, ms);
});

ids.reduce((previousPromise, id) => {
  return previousPromise.then(() => {
    return timeout(() => console.log(id), 200);
  });
}, Promise.resolve());

console.log('done');

Или используя async / await:

const arr = '6,7,7,8,8,5,3,5,1'
const ids = arr.split(',');
const length = ids.length;

const timeout = (fn, ms) => new Promise(res => {
  setTimeout(() => { fn(); res(); }, ms);
});

ids.reduce(async (previousPromise, id) => {
  await previousPromise;
  return timeout(() => console.log(id), 200);
}, Promise.resolve());

console.log('done');
0 голосов
/ 09 марта 2019

Вы можете попробовать этот подход, где promisefy получит значение любого типа в качестве аргумента, который должен быть разрешен после.

const IDs = '6,7,7,8,8,5,3,5,1'.split(',');

// simulates an async response after 1s
const promisefy = (value) => new Promise((resolve) => {
  setTimeout(() => {
    resolve(value);
  }, 1000);
});

// stores all async responses
const responses = IDs.map((id, index) => {
  return promisefy(id);
});

// executes sequentially all responses
responses.forEach((resp, index) => {
  resp.then((value) => console.log(`id: ${value}, index: ${index}`));
});

Или с помощью Array Reduce ()

// stores all async responses
const responses2 = IDs.map((id) => promisefy(id));

// executes sequentially all responses
responses2
  .reduce((_, resp, index) => {
      return resp.then((value) => console.log(`id: ${value}, index: ${index}`));
    }, null)
  .then(() => console.log('done'));
...