为什么这个map reduce Promises数组不起作用,而只是减少它起作用

why this map reduce Promises array does not work, but just reducing it does?

本文关键字:起作用 不起作用 map reduce 数组 Promises 为什么      更新时间:2023-09-26

假设您有一个异步进程,它是n个异步步骤的级联:

function step1(item){
  return new Promise((resolve, reject)=>{
    setTimeout(()=>{
      resolve('step 1 fort item ' + item);
    });
  });
}
function step2(item){
  return new Promise((resolve, reject)=>{
    setTimeout(()=>{
      resolve('step 2 for item ' + item);
    },1000);
  });
}
function step3(item){
  return new Promise((resolve, reject)=>{
    setTimeout(()=>{
      resolve('step 3 for item ' + item);
    },1000);
  });
}
function processItem(item){
  let steps = [step1, step2, step3];
  return steps.reduce((current, next) => {
    return current.then(res => {
      console.log(res);
      return next(item);
    }).then(res => {
      console.log(res);
    });
  },Promise.resolve());
}

因此,现在您有了一个items数组,并且您希望处理所有项,并将函数processItem应用于每个项。但由于限制,所有进程都必须按顺序执行,一个进程在前一个进程完成时开始。

好吧,如果我用这种方式实现它:

let items = [1, 2, 3];
items.map(i => processItem(i)).reduce((p, next) => {
  return p.then(() => {
    return next;
  });
}).then(() => {
  // all finished
});

你会得到这样的输出:

step 1 for item 1
step 1 for item 2
step 1 for item 3
step 2 for item 1
step 2 for item 2
step 2 for item 3
step 3 for item 1
step 3 for item 2
step 3 for item 3

并且只需要3秒,而不是预期的9秒。

同时,如果你避免地图步骤来实现它,你会得到预期的结果:

let items = [1, 2, 3];
items.reduce((promise, nextItem) => {
  return promise.then(() => {
    return processItem(nextItem);
  });
}, Promise.resolve()).then(() => {
  // all finished
});

结果:

step 1 for item 1
step 2 for item 1
step 3 for item 1
step 1 for item 2
step 2 for item 2
step 3 for item 2
step 1 for item 3
step 2 for item 3
step 3 for item 3

并且需要预期的9秒。

为什么会发生这种情况?我认为,当你将一个promise返回函数映射到一个数组时,你会得到一个promises数组,所以reduce匿名函数的第一个和第二个参数就是promise,你可以像我在上面第一个例子中所做的那样。我对此有点困惑。

.map正在调用传递给它的函数,如果您不希望在这一步调用processItem,则需要再次包装它,即

let items = [1, 2, 3];
items.map(i => () => processItem(i)).reduce(
    (p, next) => p.then(next),
    Promise.resolve()
).then(() => {
    // all finished
});

谢谢@Paul S.

以你的答案为基础,我想我更喜欢包装原始的processItem函数:

function processItem(item){
  return function(){
    let steps = [step1, step2, step3];
    return steps.reduce((current, next) => {
      return current.then(res => {
        console.log(res);
        return next(item);
      }).then(res => {
        console.log(res);
      });
    },Promise.resolve());
  }
}

然后地图会变得更干净:

items = [1, 2, 3];
items.map(i => processItem(i)).reduce((p, next) => {
    return p.then(() => {
        return next();
    });
}, Promise.resolve()).then(() => {
    // all finished
});