运行 多个递归 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 本身是否在数组中,但我希望有更优雅的方法。
这是使用异步生成器的示例,其中可以使用 return
或 break
停止迭代
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
我们对 Color
、stepColor
和 setElemColor
(以及其他)做了一些一厢情愿的想法,让我们先实现这些
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元素的颜色
最后,我们编写助手 delay
和 effect
。 delay
将创建一个在 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>
我正在开发一个 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 本身是否在数组中,但我希望有更优雅的方法。
这是使用异步生成器的示例,其中可以使用 return
或 break
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
我们对 Color
、stepColor
和 setElemColor
(以及其他)做了一些一厢情愿的想法,让我们先实现这些
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元素的颜色
最后,我们编写助手 delay
和 effect
。 delay
将创建一个在 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>