在 foreach 循环中使用回调函数

function with callback in foreach loop

我正在尝试在 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。我不确定这是否是我想要的,因为我正在努力避免回调地狱。

解决方法是promisify回调函数然后使用Array.prototype.map() coupled with 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 遵循 (error, result) => ... 的 Node.js 约定,那么你应该使用 util.promisify() 来 promisify 函数,或者查看文档以查看是否省略回调参数将导致调用 return 承诺,因为许多包现在提供基于承诺的 API 开箱即用。

如果您想确保异步操作不是 运行 并行而是一个接一个地进行,请继续阅读。如果您想按顺序获得结果但不关心它们是否 运行 并行,请参阅 .

您可以使用 async 函数在 for 循环中 await 承诺,因为承诺的计时器非常有用:

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")
)();

您可以使用 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');

您可以尝试这种方法,其中 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'));