【问题标题】:How to await an Array of async Tasks without blowing the stack?如何在不破坏堆栈的情况下等待异步任务数组?
【发布时间】:2021-01-25 06:52:45
【问题描述】:

如果您想等待所有 Tasks 的大数组,即使数组折叠是堆栈安全的,也会炸毁堆栈,因为它会产生一个大的延迟函数调用树:

const record = (type, o) =>
  (o[type.name || type] = type.name || type, o);

const thisify = f => f({});

const arrFold = f => init => xs => {
  let acc = init;
  
  for (let i = 0; i < xs.length; i++)
    acc = f(acc) (xs[i], i);

  return acc;
};

const Task = task => record(
  Task,
  thisify(o => {
    o.task = (res, rej) =>
      task(x => {
        o.task = k => k(x);
        return res(x);
      }, rej);
    
    return o;
  }));

const taskMap = f => tx =>
  Task((res, rej) =>
    tx.task(x => res(f(x)), rej));

const taskOf = x =>
  Task((res, rej) => res(x));

const taskAnd = tx => ty =>
  Task((res, rej) =>
    tx.task(x =>
      ty.task(y =>
        res([x, y]), rej), rej));

const taskAll =
  arrFold(tx => ty =>
    taskMap(([x, y]) =>
      xs => x => xs.concat([x]))
        (taskAnd(tx) (ty)))
          (taskOf([]));

const inc = x =>
  Task((res, rej) =>
    setTimeout(x => res(x + 1), 0, x));
    
const xs = Array(1e5).fill(inc(0));

const main = taskAll(xs);

main.task(console.log, console.error);

为了解决这个问题,您通常会使用特殊的数据结构和相应的蹦床来中断函数调用:

const Call = f => (...args) =>
  ({tag: "Call", f, args});

const deferredRec = step => {
  while (step && step.tag === "Call")
    step = step.f(...step.args);

  return step;
};

现在taskAll 中的决定性函数似乎是taskMap,其中两个操作结束了堆栈:

const taskMap = f => tx =>
  Task((res, rej) =>
    tx.task(x => res(f(x)), rej));
//               ^^^^^^^^^
//  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^

const taskMap = f => tx =>
  Task((res, rej) =>
    Call(f => tx.task(f)) (x => Call(res) (f(x)), rej));

虽然调整防止了堆栈溢出,但不幸的是,它阻止了计算运行到完成,也就是说,最终的延续 console.log 永远不会被调用,但在调用 inc 一次后计算会停止(参见行 A ):

const deferredRec = step => {
  while (step && step.tag === "Call")
    step = step.f(...step.args);

  return step;
};

const Call = f => (...args) =>
  ({tag: "Call", f, args});

const record = (type, o) =>
  (o[type.name || type] = type.name || type, o);

const thisify = f => f({});

const arrFold = f => init => xs => {
  let acc = init;
  
  for (let i = 0; i < xs.length; i++)
    acc = f(acc) (xs[i], i);

  return acc;
};

const Task = task => record(
  Task,
  thisify(o => {
    o.task = (res, rej) =>
      task(x => {
        o.task = k => k(x);
        return res(x);
      }, rej);
    
    return o;
  }));

const taskMap = f => tx =>
  Task((res, rej) =>
    Call(f => tx.task(f)) (x => Call(res) (f(x)), rej));

const taskOf = x =>
  Task((res, rej) => res(x));

const taskAnd = tx => ty =>
  Task((res, rej) =>
    tx.task(x =>
      ty.task(y =>
        res([x, y]), rej), rej));

const taskAll =
  arrFold(tx => ty =>
    taskMap(([xs, x]) =>
      xs.concat([x]))
        (taskAnd(tx) (ty)))
          (taskOf([]));

const inc = x =>
  Task((res, rej) =>
    setTimeout(x => (console.log("inc"), res(x + 1)), 0, x)); // A
    
const xs = Array(3).fill(inc(0));

const main = taskAll(xs);

deferredRec(main.task(console.log, console.error));

如何才能正确地做到这一点?对于各种 CPS 代码是否有更通用的方法?请注意,我不想放弃懒惰。

【问题讨论】:

  • "...调用一次 inc 后计算停止(见 A 行):" - 因为 Array.prototype.fill() 就是这样工作的。它将inc(0) 返回的值分配给数组的所有点。它不会为每个索引调用一次 inc(0)xs[0] === xs[1] === xs[2]

标签: javascript functional-programming lazy-evaluation continuations continuation-passing


【解决方案1】:

我自己想通了。 inc (line A) 只需应用蹦床:

const deferredRec = step => {
  while (step && step.tag === "Call")
    step = step.f(...step.args);

  return step;
};

const Call = f => (...args) =>
  ({tag: "Call", f, args});

const record = (type, o) =>
  (o[type.name || type] = type.name || type, o);

const thisify = f => f({});

const arrFold = f => init => xs => {
  let acc = init;
  
  for (let i = 0; i < xs.length; i++)
    acc = f(acc) (xs[i], i);

  return acc;
};

const Task = task => record(
  Task,
  thisify(o => {
    o.task = (res, rej) =>
      task(x => {
        o.task = k => k(x);
        return res(x);
      }, rej);
    
    return o;
  }));

const taskMap = f => tx =>
  Task((res, rej) =>
    Call(f => tx.task(f)) (x => Call(res) (f(x)), rej));

const taskOf = x =>
  Task((res, rej) => res(x));

const taskAnd = tx => ty =>
  Task((res, rej) =>
    tx.task(x =>
      ty.task(y =>
        res([x, y]), rej), rej));

const taskAll =
  arrFold(tx => ty =>
    taskMap(([xs, x]) =>
      xs.concat([x]))
        (taskAnd(tx) (ty)))
          (taskOf([]));

const inc = x =>
  Task((res, rej) =>
    setTimeout(x => deferredRec(res(x + 1)), 0, x)); // A
    
const xs = Array(1e4).fill(inc(0));

const main = taskAll(xs);

deferredRec(main.task(console.log, console.error));

【讨论】:

    猜你喜欢
    • 2013-08-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-07-23
    • 2021-12-19
    • 2018-05-28
    • 1970-01-01
    • 2012-01-30
    相关资源
    最近更新 更多