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

时间:2018-01-19 21:21:26

标签: javascript recursion bluebird

我正在研究一种LED条带动画工具,它允许用户选择可同时运行的多种效果。每个效果都是(蓝鸟)的承诺。有一种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()

我可以使用某种数据结构来切换递归承诺吗?换句话说,我如何通知效果(递归承诺)以停止递归?

我在考虑一个包含所有承诺的数组。 但是当我不再在阵列中时,我不知道如何打破/解决递归承诺。我可以在return之前检查一下承诺本身是否在数组内部,但我希望有一种更优雅的方式。

3 个答案:

答案 0 :(得分:1)

让我们看一个简单的递归函数,它在高级别表达我们的程序

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 }

现在我们有了一种创造色彩的方法,然后获得了#34;下一个&#34;颜色,我们可以设置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

要运行程序,只需使用input元素调用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)

这是一个有效的演示

&#13;
&#13;
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)
&#13;
#main {
  width: 100px;
  height: 100px;
  background-color: rgb(128, 128, 128);
}
&#13;
<div id="main"></div>
<p>runs for 5 seconds...</p>
&#13;
&#13;
&#13;

答案 1 :(得分:0)

这是使用异步生成器的示例,其中可以使用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)

答案 2 :(得分:0)

我可能会因为对你的问题的解释而偏离基础,但我认为你在所有递归完成后都试图解决最初的承诺。如果这就是您所要求的,您可以将初始承诺传递给每个递归调用。

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

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);
});