JavaScript ES6 promise for 循环
JavaScript ES6 promise for loop
for (let i = 0; i < 10; i++) {
const promise = new Promise((resolve, reject) => {
const timeout = Math.random() * 1000;
setTimeout(() => {
console.log(i);
}, timeout);
});
// TODO: Chain this promise to the previous one (maybe without having it running?)
}
以上将给出以下随机输出:
6
9
4
8
5
1
7
2
3
0
任务很简单:确保每个 promise 仅在另一个 promise 之后运行 (.then()
)。
出于某种原因,我找不到方法。
我尝试了生成器函数 (yield
),尝试了 return 一个承诺的简单函数,但最终它总是归结为同样的问题:循环是同步的.
对于 async 我会简单地使用 async.series()
.
你是怎么解决的?
正如您已经在问题中暗示的那样,您的代码会同步创建所有承诺。相反,它们应该只在前一个解决时创建。
其次,每个使用 new Promise
创建的承诺都需要通过调用 resolve
(或 reject
)来解决。这应该在计时器到期时完成。这将触发您对该承诺的任何 then
回调。为了实现链条,这样的 then
回调(或 await
)是必要的。
有了这些成分,有几种方法可以执行此异步链接:
使用一个 for
循环,以立即解决的承诺开始
Array#reduce
以立即解决的承诺开始
使用将自身作为解析回调传递的函数
与ECMAScript2017的async
/ await
syntax
与ECMAScript2020的for await...of
syntax
但让我先介绍一个非常有用的通用函数。
很有前途setTimeout
使用setTimeout
很好,但我们实际上需要一个在计时器到期时解析的承诺。因此,让我们创建这样一个函数:这称为 promisifying 函数,在这种情况下我们将 promisify setTimeout
。它将提高代码的可读性,并可用于以上所有选项:
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
查看下面每个选项的片段和评论。
1。随着 for
您可以使用for
循环,但您必须确保它不会同步创建所有承诺。相反,您创建一个初始的立即解决的承诺,然后在之前的承诺解决时链接新的承诺:
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
for (let i = 0, p = Promise.resolve(); i < 10; i++) {
p = p.then(() => delay(Math.random() * 1000))
.then(() => console.log(i));
}
所以这段代码创建了一长串 then
调用。变量 p
仅用于不丢失对该链的跟踪,并允许循环的下一次迭代在同一链上继续。回调将在同步循环完成后开始执行。
重要的是 then
-回调 returns delay()
创建的承诺:这将确保异步链接。
2。随着 reduce
这只是对先前策略的一种更实用的方法。您创建一个与要执行的链长度相同的数组,并从立即解析的承诺开始:
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
[...Array(10)].reduce( (p, _, i) =>
p.then(() => delay(Math.random() * 1000))
.then(() => console.log(i))
, Promise.resolve() );
当您实际上有一个包含要在承诺中使用的数据的数组时,这可能更有用。
3。使用函数将自身作为 resolution-callback
这里我们创建一个函数并立即调用它。它同步创建第一个承诺。解决后,再次调用该函数:
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
(function loop(i) {
if (i >= 10) return; // all done
delay(Math.random() * 1000).then(() => {
console.log(i);
loop(i+1);
});
})(0);
这创建了一个名为 loop
的函数,在代码的最后你可以看到它被立即调用,参数为 0。这是计数器,i 参数。如果该计数器仍低于 10,该函数将创建一个新的承诺,否则链接将停止。
当delay()
resolve 时,会触发then
回调,再次调用该函数。
4。用 async
/await
现代 JS 引擎 support this syntax:
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
(async function loop() {
for (let i = 0; i < 10; i++) {
await delay(Math.random() * 1000);
console.log(i);
}
})();
它可能看起来很奇怪,因为它 似乎 承诺是同步创建的,但实际上 async
函数 returns 当它执行第一个 await
时。每次等待的 promise 解析时,函数的 运行 上下文都会恢复,并在 await
之后继续,直到遇到下一个,如此继续直到循环完成。
5。随着 for await...of
借助 EcmaScript 2020,for await...of
找到了通往现代 JavaScript 引擎的道路。虽然在这种情况下并没有真正减少代码,但它允许将随机区间链的定义与它的实际迭代隔离开来:
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
async function * randomDelays(count, max) {
for (let i = 0; i < count; i++) yield delay(Math.random() * max).then(() => i);
}
(async function loop() {
for await (let i of randomDelays(10, 1000)) console.log(i);
})();
您可以为此使用 async/await
。我会解释更多,但实际上没有什么。这只是一个常规的 for
循环,但我在构建 Promise
之前添加了 await
关键字
我喜欢的是您的 Promise 可以解析正常值,而不是像您的代码(或此处的其他答案)那样产生副作用。这给了你像 塞尔达传说:Link 回到过去 中的力量,你可以影响光明世界 和 中的事物] 黑暗世界——也就是说,你可以轻松地使用数据 before/after 承诺的数据是可用的,而不必求助于深度嵌套的函数、其他笨重的控制结构或愚蠢的 IIFEs。
// where DarkWorld is in the scary, unknown future
// where LightWorld is the world we saved from Ganondorf
LightWorld ... await DarkWorld
这就是它的样子......
async function someProcedure (n) {
for (let i = 0; i < n; i++) {
const t = Math.random() * 1000
const x = await new Promise(r => setTimeout(r, t, i))
console.log (i, x)
}
return 'done'
}
someProcedure(10)
.then(console.log)
.catch(console.error)
0 0
1 1
2 2
3 3
4 4
5 5
6 6
7 7
8 8
9 9
done
看看我们如何不必在我们的程序中处理那个麻烦的 .then
调用? async
关键字将自动确保返回 Promise
,因此我们可以对返回值链接 .then
调用。这让我们取得了巨大的成功:运行 n
承诺的顺序,然后 做一些重要的事情——比如显示一条 success/error 消息。
基于 trincot 的出色回答,我编写了一个可重用函数,该函数接受一个处理程序以 运行 处理数组中的每个项目。函数本身 return 是一个允许您等待直到循环结束的承诺,并且您传递的处理函数也可能 return 一个承诺。
循环(项目,处理程序):承诺
我花了一些时间才弄好,但我相信下面的代码在很多 promise 循环的情况下都是可用的。
复制粘贴代码:
// SEE
const loop = (arr, fn, busy, err, i=0) => {
const body = (ok,er) => {
try {const r = fn(arr[i], i, arr); r && r.then ? r.then(ok).catch(er) : ok(r)}
catch(e) {er(e)}
}
const next = (ok,er) => () => loop(arr, fn, ok, er, ++i)
const run = (ok,er) => i < arr.length ? new Promise(body).then(next(ok,er)).catch(er) : ok()
return busy ? run(busy,err) : new Promise(run)
}
用法
要使用它,请将要循环的数组作为第一个参数,将处理函数作为第二个参数来调用它。第三、四、五参数不传参,内部使用
const loop = (arr, fn, busy, err, i=0) => {
const body = (ok,er) => {
try {const r = fn(arr[i], i, arr); r && r.then ? r.then(ok).catch(er) : ok(r)}
catch(e) {er(e)}
}
const next = (ok,er) => () => loop(arr, fn, ok, er, ++i)
const run = (ok,er) => i < arr.length ? new Promise(body).then(next(ok,er)).catch(er) : ok()
return busy ? run(busy,err) : new Promise(run)
}
const items = ['one', 'two', 'three']
loop(items, item => {
console.info(item)
})
.then(() => console.info('Done!'))
高级用例
让我们看看处理函数、嵌套循环和错误处理。
处理程序(当前、索引、全部)
处理程序传递了 3 个参数。当前项、当前项的索引和正在循环的完整数组。如果处理函数需要做异步工作,它可以 return 一个承诺,循环函数将在开始下一次迭代之前等待承诺解决。您可以嵌套循环调用,一切都按预期工作。
const loop = (arr, fn, busy, err, i=0) => {
const body = (ok,er) => {
try {const r = fn(arr[i], i, arr); r && r.then ? r.then(ok).catch(er) : ok(r)}
catch(e) {er(e)}
}
const next = (ok,er) => () => loop(arr, fn, ok, er, ++i)
const run = (ok,er) => i < arr.length ? new Promise(body).then(next(ok,er)).catch(er) : ok()
return busy ? run(busy,err) : new Promise(run)
}
const tests = [
[],
['one', 'two'],
['A', 'B', 'C']
]
loop(tests, (test, idx, all) => new Promise((testNext, testFailed) => {
console.info('Performing test ' + idx)
return loop(test, (testCase) => {
console.info(testCase)
})
.then(testNext)
.catch(testFailed)
}))
.then(() => console.info('All tests done'))
错误处理
我看过的许多 promise 循环示例在发生异常时都会崩溃。让这个函数做正确的事情非常棘手,但据我所知它现在正在工作。确保向任何内部循环添加一个 catch 处理程序,并在它发生时调用拒绝函数。例如:
const loop = (arr, fn, busy, err, i=0) => {
const body = (ok,er) => {
try {const r = fn(arr[i], i, arr); r && r.then ? r.then(ok).catch(er) : ok(r)}
catch(e) {er(e)}
}
const next = (ok,er) => () => loop(arr, fn, ok, er, ++i)
const run = (ok,er) => i < arr.length ? new Promise(body).then(next(ok,er)).catch(er) : ok()
return busy ? run(busy,err) : new Promise(run)
}
const tests = [
[],
['one', 'two'],
['A', 'B', 'C']
]
loop(tests, (test, idx, all) => new Promise((testNext, testFailed) => {
console.info('Performing test ' + idx)
loop(test, (testCase) => {
if (idx == 2) throw new Error()
console.info(testCase)
})
.then(testNext)
.catch(testFailed) // <--- DON'T FORGET!!
}))
.then(() => console.error('Oops, test should have failed'))
.catch(e => console.info('Succesfully caught error: ', e))
.then(() => console.info('All tests done'))
更新:NPM 包
自从写了这篇回答后,我把上面的代码转成了一个npm包。
for-async
安装
npm install --save for-async
导入
var forAsync = require('for-async'); // Common JS, or
import forAsync from 'for-async';
用法(异步)
var arr = ['some', 'cool', 'array'];
forAsync(arr, function(item, idx){
return new Promise(function(resolve){
setTimeout(function(){
console.info(item, idx);
// Logs 3 lines: `some 0`, `cool 1`, `array 2`
resolve(); // <-- signals that this iteration is complete
}, 25); // delay 25 ms to make async
})
})
有关详细信息,请参阅软件包自述文件。
这是我的 2 美分价值:
- 可恢复函数
forpromise()
- 模拟经典的 for 循环
- 允许根据内部逻辑提前退出,返回一个值
- 可以收集传递给 resolve/next/collect
的结果数组
- 默认start=0,increment=1
- 循环内抛出的异常被捕获并传递给 .catch()
function forpromise(lo, hi, st, res, fn) {
if (typeof res === 'function') {
fn = res;
res = undefined;
}
if (typeof hi === 'function') {
fn = hi;
hi = lo;
lo = 0;
st = 1;
}
if (typeof st === 'function') {
fn = st;
st = 1;
}
return new Promise(function(resolve, reject) {
(function loop(i) {
if (i >= hi) return resolve(res);
const promise = new Promise(function(nxt, brk) {
try {
fn(i, nxt, brk);
} catch (ouch) {
return reject(ouch);
}
});
promise.
catch (function(brkres) {
hi = lo - st;
resolve(brkres)
}).then(function(el) {
if (res) res.push(el);
loop(i + st)
});
})(lo);
});
}
//no result returned, just loop from 0 thru 9
forpromise(0, 10, function(i, next) {
console.log("iterating:", i);
next();
}).then(function() {
console.log("test result 1", arguments);
//shortform:no result returned, just loop from 0 thru 4
forpromise(5, function(i, next) {
console.log("counting:", i);
next();
}).then(function() {
console.log("test result 2", arguments);
//collect result array, even numbers only
forpromise(0, 10, 2, [], function(i, collect) {
console.log("adding item:", i);
collect("result-" + i);
}).then(function() {
console.log("test result 3", arguments);
//collect results, even numbers, break loop early with different result
forpromise(0, 10, 2, [], function(i, collect, break_) {
console.log("adding item:", i);
if (i === 8) return break_("ending early");
collect("result-" + i);
}).then(function() {
console.log("test result 4", arguments);
// collect results, but break loop on exception thrown, which we catch
forpromise(0, 10, 2, [], function(i, collect, break_) {
console.log("adding item:", i);
if (i === 4) throw new Error("failure inside loop");
collect("result-" + i);
}).then(function() {
console.log("test result 5", arguments);
}).
catch (function(err) {
console.log("caught in test 5:[Error ", err.message, "]");
});
});
});
});
});
如果你仅限于 ES6,最好的选择是 Promise all。在成功执行 array
参数中的所有承诺后,Promise.all(array)
也是 returns 一组承诺。
假设,如果你想更新数据库中的许多学生记录,下面的代码演示了 Promise.all 在这种情况下的概念 -
let promises = students.map((student, index) => {
//where students is a db object
student.rollNo = index + 1;
student.city = 'City Name';
//Update whatever information on student you want
return student.save();
});
Promise.all(promises).then(() => {
//All the save queries will be executed when .then is executed
//You can do further operations here after as all update operations are completed now
});
Map 只是循环的示例方法。您还可以使用 for
或 forin
或 forEach
循环。所以这个概念非常简单,启动要在其中执行批量异步操作的循环。将每个此类异步操作语句推送到在该循环范围之外声明的数组中。循环完成后,以准备好的queries/promises数组为参数执行Promise all语句。
基本概念是 javascript 循环是同步的,而数据库调用是异步的,我们在也是同步的循环中使用 push 方法。所以,异步行为的问题不会出现在循环内部。
我在 Angular 中创建了一个无限期循环 promise 函数的片段。您可以启动、停止或重新启动它。
您基本上需要递归调用相同的方法并等待它的当前进程,如下所示:
async autoloop(): Promise<void> {
if(this.running){
await this.runMe();
await this.autoloop();
}
return Promise.resolve();
}
JavaScript:
import { Component } from '@angular/core';
@Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent {
messages: string[] = [];
counter = 1;
running = false;
constructor() {
this.start();
}
onClick(): void {
this.running = !this.running;
if(this.running){
this.start();
}
else{
this.stop();
}
}
async onRestartClick(): Promise<void>{
await this.stop();
this.messages = [];
this.counter = 1;
this.start();
}
start(): void{
this.running = true;
this.autoloop();
}
async stop(): Promise<void>{
this.running = false;
await this.delay(1000);
}
async autoloop(): Promise<void> {
if(this.running){
await this.runMe();
await this.autoloop();
}
return Promise.resolve();
}
async runMe(): Promise<void> {
await this.delay(1000);
if(this.running){
this.messages.push(`Message ${this.counter++}`);
}
return Promise.resolve();
}
async delay(ms: number) {
await new Promise<void>((resolve) => setTimeout(() => resolve(), ms));
}
}
Html:
<h1>Endless looping a promise every 1 second</h1>
<button (click)="onClick()">Start / stop</button>
<button (click)="onRestartClick()">Restart</button>
<p *ngFor="let message of messages">
{{message}}
</p>
看到上面的回答,感觉很迷茫。我根据答案的灵感编写了以下代码。我觉得它的逻辑比较明显,我调用函数来代替原来的for循环:
async function pointToCountry(world, data) { // data is for loop array
if (data.length > 0) { // for condition
const da = data.shift(); // get current data and modified data one row code
// some business logic
msg = da.info
pointofView(world, da);
// await the current task
await new Promise(r => setTimeout(_ => {
r() // resolve and finish the current task
}, 5000))
// call itself and enter the next loop
pointToCountry(world, data)
} else { // business logic after all tasks
pointofView(world, { longitude: 0, latitude: 0 });
world.controls().autoRotate = true;
}
}
for (let i = 0; i < 10; i++) {
const promise = new Promise((resolve, reject) => {
const timeout = Math.random() * 1000;
setTimeout(() => {
console.log(i);
}, timeout);
});
// TODO: Chain this promise to the previous one (maybe without having it running?)
}
以上将给出以下随机输出:
6
9
4
8
5
1
7
2
3
0
任务很简单:确保每个 promise 仅在另一个 promise 之后运行 (.then()
)。
出于某种原因,我找不到方法。
我尝试了生成器函数 (yield
),尝试了 return 一个承诺的简单函数,但最终它总是归结为同样的问题:循环是同步的.
对于 async 我会简单地使用 async.series()
.
你是怎么解决的?
正如您已经在问题中暗示的那样,您的代码会同步创建所有承诺。相反,它们应该只在前一个解决时创建。
其次,每个使用 new Promise
创建的承诺都需要通过调用 resolve
(或 reject
)来解决。这应该在计时器到期时完成。这将触发您对该承诺的任何 then
回调。为了实现链条,这样的 then
回调(或 await
)是必要的。
有了这些成分,有几种方法可以执行此异步链接:
使用一个
for
循环,以立即解决的承诺开始Array#reduce
以立即解决的承诺开始使用将自身作为解析回调传递的函数
与ECMAScript2017的
async
/await
syntax与ECMAScript2020的
for await...of
syntax
但让我先介绍一个非常有用的通用函数。
很有前途setTimeout
使用setTimeout
很好,但我们实际上需要一个在计时器到期时解析的承诺。因此,让我们创建这样一个函数:这称为 promisifying 函数,在这种情况下我们将 promisify setTimeout
。它将提高代码的可读性,并可用于以上所有选项:
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
查看下面每个选项的片段和评论。
1。随着 for
您可以使用for
循环,但您必须确保它不会同步创建所有承诺。相反,您创建一个初始的立即解决的承诺,然后在之前的承诺解决时链接新的承诺:
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
for (let i = 0, p = Promise.resolve(); i < 10; i++) {
p = p.then(() => delay(Math.random() * 1000))
.then(() => console.log(i));
}
所以这段代码创建了一长串 then
调用。变量 p
仅用于不丢失对该链的跟踪,并允许循环的下一次迭代在同一链上继续。回调将在同步循环完成后开始执行。
重要的是 then
-回调 returns delay()
创建的承诺:这将确保异步链接。
2。随着 reduce
这只是对先前策略的一种更实用的方法。您创建一个与要执行的链长度相同的数组,并从立即解析的承诺开始:
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
[...Array(10)].reduce( (p, _, i) =>
p.then(() => delay(Math.random() * 1000))
.then(() => console.log(i))
, Promise.resolve() );
当您实际上有一个包含要在承诺中使用的数据的数组时,这可能更有用。
3。使用函数将自身作为 resolution-callback
这里我们创建一个函数并立即调用它。它同步创建第一个承诺。解决后,再次调用该函数:
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
(function loop(i) {
if (i >= 10) return; // all done
delay(Math.random() * 1000).then(() => {
console.log(i);
loop(i+1);
});
})(0);
这创建了一个名为 loop
的函数,在代码的最后你可以看到它被立即调用,参数为 0。这是计数器,i 参数。如果该计数器仍低于 10,该函数将创建一个新的承诺,否则链接将停止。
当delay()
resolve 时,会触发then
回调,再次调用该函数。
4。用 async
/await
现代 JS 引擎 support this syntax:
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
(async function loop() {
for (let i = 0; i < 10; i++) {
await delay(Math.random() * 1000);
console.log(i);
}
})();
它可能看起来很奇怪,因为它 似乎 承诺是同步创建的,但实际上 async
函数 returns 当它执行第一个 await
时。每次等待的 promise 解析时,函数的 运行 上下文都会恢复,并在 await
之后继续,直到遇到下一个,如此继续直到循环完成。
5。随着 for await...of
借助 EcmaScript 2020,for await...of
找到了通往现代 JavaScript 引擎的道路。虽然在这种情况下并没有真正减少代码,但它允许将随机区间链的定义与它的实际迭代隔离开来:
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
async function * randomDelays(count, max) {
for (let i = 0; i < count; i++) yield delay(Math.random() * max).then(() => i);
}
(async function loop() {
for await (let i of randomDelays(10, 1000)) console.log(i);
})();
您可以为此使用 async/await
。我会解释更多,但实际上没有什么。这只是一个常规的 for
循环,但我在构建 Promise
await
关键字
我喜欢的是您的 Promise 可以解析正常值,而不是像您的代码(或此处的其他答案)那样产生副作用。这给了你像 塞尔达传说:Link 回到过去 中的力量,你可以影响光明世界 和 中的事物] 黑暗世界——也就是说,你可以轻松地使用数据 before/after 承诺的数据是可用的,而不必求助于深度嵌套的函数、其他笨重的控制结构或愚蠢的 IIFEs。
// where DarkWorld is in the scary, unknown future
// where LightWorld is the world we saved from Ganondorf
LightWorld ... await DarkWorld
这就是它的样子......
async function someProcedure (n) {
for (let i = 0; i < n; i++) {
const t = Math.random() * 1000
const x = await new Promise(r => setTimeout(r, t, i))
console.log (i, x)
}
return 'done'
}
someProcedure(10)
.then(console.log)
.catch(console.error)
0 0
1 1
2 2
3 3
4 4
5 5
6 6
7 7
8 8
9 9
done
看看我们如何不必在我们的程序中处理那个麻烦的 .then
调用? async
关键字将自动确保返回 Promise
,因此我们可以对返回值链接 .then
调用。这让我们取得了巨大的成功:运行 n
承诺的顺序,然后 做一些重要的事情——比如显示一条 success/error 消息。
基于 trincot 的出色回答,我编写了一个可重用函数,该函数接受一个处理程序以 运行 处理数组中的每个项目。函数本身 return 是一个允许您等待直到循环结束的承诺,并且您传递的处理函数也可能 return 一个承诺。
循环(项目,处理程序):承诺
我花了一些时间才弄好,但我相信下面的代码在很多 promise 循环的情况下都是可用的。
复制粘贴代码:
// SEE
const loop = (arr, fn, busy, err, i=0) => {
const body = (ok,er) => {
try {const r = fn(arr[i], i, arr); r && r.then ? r.then(ok).catch(er) : ok(r)}
catch(e) {er(e)}
}
const next = (ok,er) => () => loop(arr, fn, ok, er, ++i)
const run = (ok,er) => i < arr.length ? new Promise(body).then(next(ok,er)).catch(er) : ok()
return busy ? run(busy,err) : new Promise(run)
}
用法
要使用它,请将要循环的数组作为第一个参数,将处理函数作为第二个参数来调用它。第三、四、五参数不传参,内部使用
const loop = (arr, fn, busy, err, i=0) => {
const body = (ok,er) => {
try {const r = fn(arr[i], i, arr); r && r.then ? r.then(ok).catch(er) : ok(r)}
catch(e) {er(e)}
}
const next = (ok,er) => () => loop(arr, fn, ok, er, ++i)
const run = (ok,er) => i < arr.length ? new Promise(body).then(next(ok,er)).catch(er) : ok()
return busy ? run(busy,err) : new Promise(run)
}
const items = ['one', 'two', 'three']
loop(items, item => {
console.info(item)
})
.then(() => console.info('Done!'))
高级用例
让我们看看处理函数、嵌套循环和错误处理。
处理程序(当前、索引、全部)
处理程序传递了 3 个参数。当前项、当前项的索引和正在循环的完整数组。如果处理函数需要做异步工作,它可以 return 一个承诺,循环函数将在开始下一次迭代之前等待承诺解决。您可以嵌套循环调用,一切都按预期工作。
const loop = (arr, fn, busy, err, i=0) => {
const body = (ok,er) => {
try {const r = fn(arr[i], i, arr); r && r.then ? r.then(ok).catch(er) : ok(r)}
catch(e) {er(e)}
}
const next = (ok,er) => () => loop(arr, fn, ok, er, ++i)
const run = (ok,er) => i < arr.length ? new Promise(body).then(next(ok,er)).catch(er) : ok()
return busy ? run(busy,err) : new Promise(run)
}
const tests = [
[],
['one', 'two'],
['A', 'B', 'C']
]
loop(tests, (test, idx, all) => new Promise((testNext, testFailed) => {
console.info('Performing test ' + idx)
return loop(test, (testCase) => {
console.info(testCase)
})
.then(testNext)
.catch(testFailed)
}))
.then(() => console.info('All tests done'))
错误处理
我看过的许多 promise 循环示例在发生异常时都会崩溃。让这个函数做正确的事情非常棘手,但据我所知它现在正在工作。确保向任何内部循环添加一个 catch 处理程序,并在它发生时调用拒绝函数。例如:
const loop = (arr, fn, busy, err, i=0) => {
const body = (ok,er) => {
try {const r = fn(arr[i], i, arr); r && r.then ? r.then(ok).catch(er) : ok(r)}
catch(e) {er(e)}
}
const next = (ok,er) => () => loop(arr, fn, ok, er, ++i)
const run = (ok,er) => i < arr.length ? new Promise(body).then(next(ok,er)).catch(er) : ok()
return busy ? run(busy,err) : new Promise(run)
}
const tests = [
[],
['one', 'two'],
['A', 'B', 'C']
]
loop(tests, (test, idx, all) => new Promise((testNext, testFailed) => {
console.info('Performing test ' + idx)
loop(test, (testCase) => {
if (idx == 2) throw new Error()
console.info(testCase)
})
.then(testNext)
.catch(testFailed) // <--- DON'T FORGET!!
}))
.then(() => console.error('Oops, test should have failed'))
.catch(e => console.info('Succesfully caught error: ', e))
.then(() => console.info('All tests done'))
更新:NPM 包
自从写了这篇回答后,我把上面的代码转成了一个npm包。
for-async
安装
npm install --save for-async
导入
var forAsync = require('for-async'); // Common JS, or
import forAsync from 'for-async';
用法(异步)
var arr = ['some', 'cool', 'array'];
forAsync(arr, function(item, idx){
return new Promise(function(resolve){
setTimeout(function(){
console.info(item, idx);
// Logs 3 lines: `some 0`, `cool 1`, `array 2`
resolve(); // <-- signals that this iteration is complete
}, 25); // delay 25 ms to make async
})
})
有关详细信息,请参阅软件包自述文件。
这是我的 2 美分价值:
- 可恢复函数
forpromise()
- 模拟经典的 for 循环
- 允许根据内部逻辑提前退出,返回一个值
- 可以收集传递给 resolve/next/collect 的结果数组
- 默认start=0,increment=1
- 循环内抛出的异常被捕获并传递给 .catch()
function forpromise(lo, hi, st, res, fn) {
if (typeof res === 'function') {
fn = res;
res = undefined;
}
if (typeof hi === 'function') {
fn = hi;
hi = lo;
lo = 0;
st = 1;
}
if (typeof st === 'function') {
fn = st;
st = 1;
}
return new Promise(function(resolve, reject) {
(function loop(i) {
if (i >= hi) return resolve(res);
const promise = new Promise(function(nxt, brk) {
try {
fn(i, nxt, brk);
} catch (ouch) {
return reject(ouch);
}
});
promise.
catch (function(brkres) {
hi = lo - st;
resolve(brkres)
}).then(function(el) {
if (res) res.push(el);
loop(i + st)
});
})(lo);
});
}
//no result returned, just loop from 0 thru 9
forpromise(0, 10, function(i, next) {
console.log("iterating:", i);
next();
}).then(function() {
console.log("test result 1", arguments);
//shortform:no result returned, just loop from 0 thru 4
forpromise(5, function(i, next) {
console.log("counting:", i);
next();
}).then(function() {
console.log("test result 2", arguments);
//collect result array, even numbers only
forpromise(0, 10, 2, [], function(i, collect) {
console.log("adding item:", i);
collect("result-" + i);
}).then(function() {
console.log("test result 3", arguments);
//collect results, even numbers, break loop early with different result
forpromise(0, 10, 2, [], function(i, collect, break_) {
console.log("adding item:", i);
if (i === 8) return break_("ending early");
collect("result-" + i);
}).then(function() {
console.log("test result 4", arguments);
// collect results, but break loop on exception thrown, which we catch
forpromise(0, 10, 2, [], function(i, collect, break_) {
console.log("adding item:", i);
if (i === 4) throw new Error("failure inside loop");
collect("result-" + i);
}).then(function() {
console.log("test result 5", arguments);
}).
catch (function(err) {
console.log("caught in test 5:[Error ", err.message, "]");
});
});
});
});
});
如果你仅限于 ES6,最好的选择是 Promise all。在成功执行 array
参数中的所有承诺后,Promise.all(array)
也是 returns 一组承诺。
假设,如果你想更新数据库中的许多学生记录,下面的代码演示了 Promise.all 在这种情况下的概念 -
let promises = students.map((student, index) => {
//where students is a db object
student.rollNo = index + 1;
student.city = 'City Name';
//Update whatever information on student you want
return student.save();
});
Promise.all(promises).then(() => {
//All the save queries will be executed when .then is executed
//You can do further operations here after as all update operations are completed now
});
Map 只是循环的示例方法。您还可以使用 for
或 forin
或 forEach
循环。所以这个概念非常简单,启动要在其中执行批量异步操作的循环。将每个此类异步操作语句推送到在该循环范围之外声明的数组中。循环完成后,以准备好的queries/promises数组为参数执行Promise all语句。
基本概念是 javascript 循环是同步的,而数据库调用是异步的,我们在也是同步的循环中使用 push 方法。所以,异步行为的问题不会出现在循环内部。
我在 Angular 中创建了一个无限期循环 promise 函数的片段。您可以启动、停止或重新启动它。
您基本上需要递归调用相同的方法并等待它的当前进程,如下所示:
async autoloop(): Promise<void> {
if(this.running){
await this.runMe();
await this.autoloop();
}
return Promise.resolve();
}
JavaScript:
import { Component } from '@angular/core';
@Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent {
messages: string[] = [];
counter = 1;
running = false;
constructor() {
this.start();
}
onClick(): void {
this.running = !this.running;
if(this.running){
this.start();
}
else{
this.stop();
}
}
async onRestartClick(): Promise<void>{
await this.stop();
this.messages = [];
this.counter = 1;
this.start();
}
start(): void{
this.running = true;
this.autoloop();
}
async stop(): Promise<void>{
this.running = false;
await this.delay(1000);
}
async autoloop(): Promise<void> {
if(this.running){
await this.runMe();
await this.autoloop();
}
return Promise.resolve();
}
async runMe(): Promise<void> {
await this.delay(1000);
if(this.running){
this.messages.push(`Message ${this.counter++}`);
}
return Promise.resolve();
}
async delay(ms: number) {
await new Promise<void>((resolve) => setTimeout(() => resolve(), ms));
}
}
Html:
<h1>Endless looping a promise every 1 second</h1>
<button (click)="onClick()">Start / stop</button>
<button (click)="onRestartClick()">Restart</button>
<p *ngFor="let message of messages">
{{message}}
</p>
看到上面的回答,感觉很迷茫。我根据答案的灵感编写了以下代码。我觉得它的逻辑比较明显,我调用函数来代替原来的for循环:
async function pointToCountry(world, data) { // data is for loop array
if (data.length > 0) { // for condition
const da = data.shift(); // get current data and modified data one row code
// some business logic
msg = da.info
pointofView(world, da);
// await the current task
await new Promise(r => setTimeout(_ => {
r() // resolve and finish the current task
}, 5000))
// call itself and enter the next loop
pointToCountry(world, data)
} else { // business logic after all tasks
pointofView(world, { longitude: 0, latitude: 0 });
world.controls().autoRotate = true;
}
}