运行 多个递归 Promise 并在请求时中断

Run multiple recursive Promises and break when requested

我正在开发一个 LED 灯带动画工具,它允许用户 select 可以同时 运行 的多种效果。每个效果都是一个(蓝鸟)承诺。有一个 run() 方法可以设置 LED 灯条的颜色。

所有承诺 运行 使用 delay 方法以固定 FPS。

run(mode) {
    return this.setStripColor(this.color).delay(1 / this.fps).then(() => { this.run(1 / this.fps) })
}

// example of an effect
rainbowSweep() {
    // .. 
    // magical unicorn code
    // ..
    return Promise.resolve().delay(1 / this.fps).then(() => {
        this.rainbowSweep()
    })

app.rainbowSweep()
app.run()

是否可以使用某种数据结构来打开和关闭递归承诺?换句话说,我如何发出停止递归的信号(递归承诺)?

我在想一个包含所有承诺的数组。 但是当它不再在数组中时,我不知道如何 break/resolve 递归承诺。我可以在 return 之前检查 promise 本身是否在数组中,但我希望有更优雅的方法。

这是使用异步生成器的示例,其中可以使用 returnbreak

停止迭代

const o = {
  value: new Map,
  done: false,
  async * gen(...props) {
    while (!this.done) {
      if (this.value.size && !this.done) {
        try {
          for (const [key, val] of this.value) {
            // yield* [{key, value: await Promise.all([].concat(val).map(prop => typeof prop === "function" ? prop() : prop)).then(values => { this.value.delete(key); return values }).catch(err => {console.log(err); return {error:[key, err]}})}];    
            // does not interrupt immediately when `this.done` is set to `true`

            for await (const value of [].concat(val).map(prop => Promise.resolve(typeof prop === "function" ? prop() : prop).then(prop => {console.log("prop:", prop); return prop}, err => {
              console.error("caught at for await:", err); return err;
            }))) {
              console.log("value:", value);
              if (key !== undefined && value !== undefined && !o.done)
                yield * [{
                  key, value
                }];
            }
            this.value.delete(key);

          }
        } catch (error) {
          console.error("caught at async:", error);
          throw error
        }
      } else {
        // yield await `no value at ${new Date()}`;
        this.done = true;
        break;
      }
    }

  }
};

(async() => {

  let n = 0;
  let caught, gen;

  try {
    gen = o.gen();
    next = new Proxy(o.value.set.bind(o.value), {
      apply(target, _, args) {
        // console.log(args);
        if (!o.done && args.length) {
          Reflect.apply(target, null, args);
          return gen.next().catch(err => {
            throw err
          })
        };
        if (!args.length && !o.done) return gen.next().catch(err => Promise.reject(err));
        if (o.done) {
          caught = "we're done here";
          return gen.throw(caught).catch(err => {throw err})
        };

      }
    });

    await next(n, Promise.resolve(0)).catch(err => {
        throw err
      })
      .then(({
        value, done
      }) => console.log(value, done));

    await next(++n, Promise.resolve(1))
      .catch(err => {
        throw err
      })
      .then(({
        value, done
      }) => console.log(value, done));

    await next(++n, [Promise.resolve(2)])
      .catch(err => {
        throw err
      })
      .then(({
        value, done
      }) => console.log(value, done));   

    await next(++n, [() => new Promise(r => setTimeout(r, 2000, 3))
                    , () => new Promise((_, reject) => setTimeout(_, 6000, 4))
                    , () => new Promise(r => setTimeout(r, 4000, 5))
                    ]
      )
      .catch(err => {
        throw err
      })
      .then(({
        value, done
      }) => console.log(value, done));

    // o.done = true; // stop generator
   while (o.value.size && !o.value.done) {
      await next()
        .catch(err => {
          throw err
        }).then(({
          value, done
        }) => {
          console.log(value, done, o.value.size);
          return
        })
   }

  } catch (e) {
    let message = await e;
    if (message === "we're done here") {
      console.error("err:", message);
    } else {
      console.error("caught at catch:", message);
      throw new Error(message)
    }
  } finally {
    console.log(gen);
    if (caught) throw new Error(caught)
    return {done: o.done, value: o.value.size}
  }

})()
.catch(err => { console.error("ok caught:", err); return err})
.then(done => console.log("done:", done));

async function* gen(n) {
  let i = 1;
  while(true) {
    let curr = yield await new Promise(r => setTimeout(r, 1000, i));
    if (curr) {
      i *= curr;
    };
    i *= 25;
    if (i > 500 * 500) break;
  }
}

async function read(n) {
  const g = gen(n);
  // while ( await g.next().then(({value, done}) => {console.log(value, done); return !done}) );
  await g.next().then(({value, done}) => {console.log(value, done); return !done});
  await g.next(100).then(({value, done}) => {console.log(value, done); return !done});
  await g.next().then(({value, done}) => {console.log(value, done); return !done});
  await g.next().then(({value, done}) => {console.log(value, done); return !done});
}

read(5)

我对你的问题的解释可能有很大偏差,但我认为你正在尝试在所有递归完成后解决最初的承诺。如果这就是您要问的,您可以将初始承诺传递给每个递归调用。

我会在我的示例中使用您的示例代码,但您缺少函数的右大括号,所以我不太确定它应该是什么样子。这是该概念的一般示例。

var recusion_counter = 0;

function myRecusivePromiseFunction(prm){
  return new Promise(done=>{
    recusion_counter++;
    prm = prm || done;
    if(recusion_counter >= 5) prm();
    else myRecusivePromiseFunction(prm);
  });
}

myRecusivePromiseFunction().then(()=>{
  console.log(recusion_counter);
});

让我们看一个简单的递归函数,它在高层次上表达了我们的程序

let RUNNING =
  true

const main = async (elem, color = Color ()) =>
  RUNNING
    ? delay (color, FPS)
        .then (effect (color => setElemColor (elem, color)))
        .then (color => main (elem, stepColor (color)))
    : color

我们对 ColorstepColorsetElemColor(以及其他)做了一些一厢情愿的想法,让我们先实现这些

const Color = (r = 128, g = 128, b = 128) =>
  ({ r, g, b })

const stepColor = ({ r, g, b }, step = 8) =>
  b < 255
    ? Color (r, g, b + step)
    : g < 255
      ? Color (r, g + step, 0)
      : r < 255
        ? Color (r + step, 0, 0)
        : Color (0, 0, 0)

const setElemColor = (elem, { r, g, b }) =>
  elem.style.backgroundColor = `rgb(${r}, ${g}, ${b})`

const c = new Color () // { r: 128, g: 128, b: 128 }
setpColor (c)          // { r: 128, g: 128, b: 136 }

现在我们有了创建颜色的方法,获取"next"颜色,我们可以设置HTML元素的颜色

最后,我们编写助手 delayeffectdelay 将创建一个在 ms 毫秒内解析的 Promised 值。 effect 用于有副作用的函数(比如设置 HTML 元素的 属性)。 FPS 只是我们的每秒帧数常量

const delay = (x, ms) =>
  new Promise (r => setTimeout (r, ms, x))

const effect = f => x =>
  (f (x), x)

const FPS =
  1000 / 30

到运行程序,只需用一个输入元素调用main。因为它是一个异步程序,所以不要忘记处理成功的 错误情况。当程序最终停止时,将输出最后使用的颜色。

main (document.querySelector('#main'))
  .then (console.log, console.error)
  // => { Color r: 136, g: 8, b: 40 }

要停止程序,随时设置RUNNING = false即可

// stop after 5 seconds
setTimeout (() => RUNNING = false, 5000)

这是一个工作演示

const Color = (r = 128, g = 128, b = 128) =>
  ({ r, g, b })

const stepColor = ({ r, g, b }, step = 16) =>
  b < 255
    ? Color (r, g, b + step)
    : g < 255
      ? Color (r, g + step, 0)
      : r < 255
        ? Color (r + step, 0, 0)
        : Color (0, 0, 0)

const setElemColor = (elem, { r, g, b }) =>
  elem.style.backgroundColor = `rgba(${r}, ${g}, ${b}, 1)`

const delay = (x, ms) =>
  new Promise (r => setTimeout (r, ms, x))

const effect = f => x =>
  (f (x), x)

const FPS =
  1000 / 60
 
let RUNNING =
  true
  
const main = async (elem, color = Color ()) =>
  RUNNING
    ? delay (color, FPS)
        .then (effect (color => setElemColor (elem, color)))
        .then (color => main (elem, stepColor (color)))
    : color

main (document.querySelector('#main'))
  .then (console.log, console.error)
  // => { r: 136, g: 8, b: 40 }
  
// stop after 5 seconds
setTimeout (() => RUNNING = false, 5000)
#main {
  width: 100px;
  height: 100px;
  background-color: rgb(128, 128, 128);
}
<div id="main"></div>
<p>runs for 5 seconds...</p>