【问题标题】:Run multiple recursive Promises and break when requested运行多个递归 Promise 并在请求时中断
【发布时间】:2018-06-29 04:53:05
【问题描述】:

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

所有的 Promise 都使用 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 之前检查一下 promise 本身是否在数组中,但我希望有更优雅的方法。

【问题讨论】:

  • Promise 只是底层异步操作的通知机制。你不会切换承诺。如果你想停止底层的异步操作,那么你直接和它通信,让它通过promise(resolve或reject)通知完成。承诺不是实际的异步操作。这只是一个通知机制。所以,如果你想停止异步操作,那么你需要直接与之通信。

标签: javascript recursion bluebird


【解决方案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 }

现在我们有了一种方法来创建颜色,获取“下一个”颜色,我们可以设置 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>

【讨论】:

  • 这如何回答 OP 的问题?
  • @jfriend00 递归运行承诺并在请求时中断?
  • 这是你认为 OP 要求的吗?这是一个非常多的代码来传达这一点。仅供参考,您不会“兑现”承诺。您运行异步操作 - Promise 提供有关异步操作的通知。
  • OP 描述了一个动画 LED;我提供了一个不到 50 行的 mvce。如果您认为main 是“非常糟糕的”,我迫不及待地想看到您对guest271314 回答的评论?
  • 仅供参考,我没有看到我说你“运行”承诺的地方
【解决方案2】:

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

【讨论】:

  • 请注意,有几种方法可以停止生成器:将o.done 设置为truereturnthrow,来自Proxy,它观察到Map 实例的getterbreak生成器
【解决方案3】:

我对您的问题的解释可能有点离题,但我认为您正在尝试在所有递归完成后解决最初的承诺。如果这是您的要求,您可以将初始承诺通过每个递归调用。

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-01-15
    • 2018-12-03
    • 1970-01-01
    • 2011-08-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多