循环使用本地承诺;
Loop with native promises;
我正在尝试使用本机 ES6 promises 创建一个异步循环 它 有点 可以工作,但是不正确。我想我在某个地方犯了一个大错误,我需要有人告诉我它在哪里以及如何正确完成
var i = 0;
//creates sample resolver
function payloadGenerator(){
return function(resolve) {
setTimeout(function(){
i++;
resolve();
}, 300)
}
}
// creates resolver that fulfills the promise if condition is false, otherwise rejects the promise.
// Used only for routing purpose
function controller(condition){
return function(resolve, reject) {
console.log('i =', i);
condition ? reject('fin') : resolve();
}
}
// creates resolver that ties payload and controller together
// When controller rejects its promise, main fulfills its thus exiting the loop
function main(){
return function(resolve, reject) {
return new Promise(payloadGenerator())
.then(function(){
return new Promise(controller(i>6))
})
.then(main(),function (err) {
console.log(err);
resolve(err)
})
.catch(function (err) {
console.log(err , 'caught');
resolve(err)
})
}
}
new Promise(main())
.catch(function(err){
console.log('caught', err);
})
.then(function(){
console.log('exit');
process.exit()
});
现在输出:
/usr/local/bin/iojs test.js
i = 1
i = 2
i = 3
i = 4
i = 5
i = 6
i = 7
fin
error: [TypeError: undefined is not a function]
error: [TypeError: undefined is not a function]
error: [TypeError: undefined is not a function]
error: [TypeError: undefined is not a function]
error: [TypeError: undefined is not a function]
error: [TypeError: undefined is not a function]
error: [TypeError: undefined is not a function]
caught [TypeError: undefined is not a function]
exit
Process finished with exit code 0
好的部分:它到达了终点。
不好的部分:它捕获了一些错误,我不知道为什么。
在捕获承诺错误时尝试记录 err.stack
而不是 err
。
在这种情况下,看起来 resolve
和 reject
未在初始迭代完成后从 main
获取 return 的匿名函数中定义。我不能完全遵循你的控制流程,但这似乎是有道理的——在 7 次迭代完成后,不应该再有任何新的承诺。但是,似乎代码仍在尝试 运行 好像有更多的承诺要解决。
编辑:这就是问题 .then(main(),function (err) {
。单独调用 main
将导致匿名函数内的 resolve
和 reject
未定义。从我的阅读方式来看,main
只能作为 Promise
构造函数的参数调用。
我见过的任何带有 promise 循环的辅助函数实际上都比你可以用递归做的更糟糕。
.thenReturn
更好一点,但是是的:
function readFile(index) {
return new Promise(function(resolve) {
setTimeout(function() {
console.log("Read file number " + (index +1));
resolve();
}, 500);
});
}
// The loop initialization
Promise.resolve(0).then(function loop(i) {
// The loop check
if (i < len) { // The post iteration increment
return readFile(i).thenReturn(i + 1).then(loop);
}
}).then(function() {
console.log("done");
}).catch(function(e) {
console.log("error", e);
});
在 jsfiddle 中查看 http://jsfiddle.net/fd1wc1ra/
这几乎完全等同于:
try {
for (var i = 0; i < len; ++i) {
readFile(i);
}
console.log("done");
} catch (e) {
console.log("error", e);
}
如果你想做嵌套循环,完全一样:
http://jsfiddle.net/fd1wc1ra/1/
function printItem(item) {
return new Promise(function(resolve) {
setTimeout(function() {
console.log("Item " + item);
resolve();
}, 500);
});
}
var mdArray = [[1,2], [3,4], [5,6]];
Promise.resolve(0).then(function loop(i) {
if (i < mdArray.length) {
var array = mdArray[i];
return Promise.resolve(0).then(function innerLoop(j) {
if (j < array.length) {
var item = array[j];
return printItem(item).thenReturn(j + 1).then(innerLoop);
}
}).thenReturn(i + 1).then(loop);
}
}).then(function() {
console.log("done");
}).catch(function(e) {
console.log("error", e);
});
如果您要做的只是用 promises 数到 7,那么这样做就可以了:
function f(p, i) {
return p.then(function() {
return new Promise(function(r) { return setTimeout(r, 300); });
})
.then(function() { console.log(i); });
}
var p = Promise.resolve();
for (var i = 0; i < 8; i++) {
p = f(p, i);
}
p.then(function() { console.log('fin'); })
.catch(function(e) { console.log(e.message); });
用 promise 循环很难,因为不落入 JavaScript 的 closures in a loop trap 几乎是不可能的,但它是可行的。上面的工作是因为它将 .then() 的所有使用推送到循环的子函数 f
中(即远离循环)。
我使用的一个更安全的解决方案是完全放弃循环并尽可能地寻找像 forEach
和 reduce
这样的模式,因为它们有效地将子功能强加给你:
[0,1,2,3,4,5,6,7].reduce(f, Promise.resolve())
.then(function() { console.log('fin'); })
.catch(function(e) { console.log(e.message); });
这里f
和上面的功能是一样的。 Try it.
更新: 你也可以使用 for (let i = 0; i < 8; i++)
来避免 "closures in a loop" 陷阱而不将代码推入子函数 f
.
PS: 你的例子中的错误是 .then(main(),
- 它需要是 .then(function() { return new Promise(main()); },
但实际上,我认为你是使用模式错误。 main()
应该 return 一个承诺,而不是被一个包裹。
我也四处寻找各种解决方案,但找不到令我满意的解决方案,所以我最终创建了自己的解决方案。以防万一它对其他人有用:
我们的想法是创建一个 promise 生成器数组,并将这个数组提供给一个辅助函数,该函数将一个接一个地执行 promise。
在我的例子中,辅助函数就是这样:
function promiseChain(chain) {
let output = new Promise((resolve, reject) => { resolve(); });
for (let i = 0; i < chain.length; i++) {
let f = chain[i];
output = output.then(f);
}
return output;
}
然后,例如,要依次加载多个网址,代码将是这样的:
// First build the array of promise generators:
let urls = [......];
let chain = [];
for (let i = 0; i < urls.length; i++) {
chain.push(() => {
return fetch(urls[i]);
});
}
// Then execute the promises one after another:
promiseChain(chain).then(() => {
console.info('All done');
});
这种方法的优点是它创建的代码相对接近于常规 for 循环,并且缩进最少。
我有类似的需求并尝试了可接受的答案,但我对操作顺序有疑问。 Promise.all
是解决方案。
function work(context) {
return new Promise((resolve, reject) => {
operation(context)
.then(result => resolve(result)
.catch(err => reject(err));
});
}
Promise
.all(arrayOfContext.map(context => work(context)))
.then(results => console.log(results))
.catch(err => console.error(err));
我正在尝试使用本机 ES6 promises 创建一个异步循环 它 有点 可以工作,但是不正确。我想我在某个地方犯了一个大错误,我需要有人告诉我它在哪里以及如何正确完成
var i = 0;
//creates sample resolver
function payloadGenerator(){
return function(resolve) {
setTimeout(function(){
i++;
resolve();
}, 300)
}
}
// creates resolver that fulfills the promise if condition is false, otherwise rejects the promise.
// Used only for routing purpose
function controller(condition){
return function(resolve, reject) {
console.log('i =', i);
condition ? reject('fin') : resolve();
}
}
// creates resolver that ties payload and controller together
// When controller rejects its promise, main fulfills its thus exiting the loop
function main(){
return function(resolve, reject) {
return new Promise(payloadGenerator())
.then(function(){
return new Promise(controller(i>6))
})
.then(main(),function (err) {
console.log(err);
resolve(err)
})
.catch(function (err) {
console.log(err , 'caught');
resolve(err)
})
}
}
new Promise(main())
.catch(function(err){
console.log('caught', err);
})
.then(function(){
console.log('exit');
process.exit()
});
现在输出:
/usr/local/bin/iojs test.js
i = 1
i = 2
i = 3
i = 4
i = 5
i = 6
i = 7
fin
error: [TypeError: undefined is not a function]
error: [TypeError: undefined is not a function]
error: [TypeError: undefined is not a function]
error: [TypeError: undefined is not a function]
error: [TypeError: undefined is not a function]
error: [TypeError: undefined is not a function]
error: [TypeError: undefined is not a function]
caught [TypeError: undefined is not a function]
exit
Process finished with exit code 0
好的部分:它到达了终点。
不好的部分:它捕获了一些错误,我不知道为什么。
在捕获承诺错误时尝试记录 err.stack
而不是 err
。
在这种情况下,看起来 resolve
和 reject
未在初始迭代完成后从 main
获取 return 的匿名函数中定义。我不能完全遵循你的控制流程,但这似乎是有道理的——在 7 次迭代完成后,不应该再有任何新的承诺。但是,似乎代码仍在尝试 运行 好像有更多的承诺要解决。
编辑:这就是问题 .then(main(),function (err) {
。单独调用 main
将导致匿名函数内的 resolve
和 reject
未定义。从我的阅读方式来看,main
只能作为 Promise
构造函数的参数调用。
我见过的任何带有 promise 循环的辅助函数实际上都比你可以用递归做的更糟糕。
.thenReturn
更好一点,但是是的:
function readFile(index) {
return new Promise(function(resolve) {
setTimeout(function() {
console.log("Read file number " + (index +1));
resolve();
}, 500);
});
}
// The loop initialization
Promise.resolve(0).then(function loop(i) {
// The loop check
if (i < len) { // The post iteration increment
return readFile(i).thenReturn(i + 1).then(loop);
}
}).then(function() {
console.log("done");
}).catch(function(e) {
console.log("error", e);
});
在 jsfiddle 中查看 http://jsfiddle.net/fd1wc1ra/
这几乎完全等同于:
try {
for (var i = 0; i < len; ++i) {
readFile(i);
}
console.log("done");
} catch (e) {
console.log("error", e);
}
如果你想做嵌套循环,完全一样:
http://jsfiddle.net/fd1wc1ra/1/
function printItem(item) {
return new Promise(function(resolve) {
setTimeout(function() {
console.log("Item " + item);
resolve();
}, 500);
});
}
var mdArray = [[1,2], [3,4], [5,6]];
Promise.resolve(0).then(function loop(i) {
if (i < mdArray.length) {
var array = mdArray[i];
return Promise.resolve(0).then(function innerLoop(j) {
if (j < array.length) {
var item = array[j];
return printItem(item).thenReturn(j + 1).then(innerLoop);
}
}).thenReturn(i + 1).then(loop);
}
}).then(function() {
console.log("done");
}).catch(function(e) {
console.log("error", e);
});
如果您要做的只是用 promises 数到 7,那么这样做就可以了:
function f(p, i) {
return p.then(function() {
return new Promise(function(r) { return setTimeout(r, 300); });
})
.then(function() { console.log(i); });
}
var p = Promise.resolve();
for (var i = 0; i < 8; i++) {
p = f(p, i);
}
p.then(function() { console.log('fin'); })
.catch(function(e) { console.log(e.message); });
用 promise 循环很难,因为不落入 JavaScript 的 closures in a loop trap 几乎是不可能的,但它是可行的。上面的工作是因为它将 .then() 的所有使用推送到循环的子函数 f
中(即远离循环)。
我使用的一个更安全的解决方案是完全放弃循环并尽可能地寻找像 forEach
和 reduce
这样的模式,因为它们有效地将子功能强加给你:
[0,1,2,3,4,5,6,7].reduce(f, Promise.resolve())
.then(function() { console.log('fin'); })
.catch(function(e) { console.log(e.message); });
这里f
和上面的功能是一样的。 Try it.
更新: for (let i = 0; i < 8; i++)
来避免 "closures in a loop" 陷阱而不将代码推入子函数 f
.
PS: 你的例子中的错误是 .then(main(),
- 它需要是 .then(function() { return new Promise(main()); },
但实际上,我认为你是使用模式错误。 main()
应该 return 一个承诺,而不是被一个包裹。
我也四处寻找各种解决方案,但找不到令我满意的解决方案,所以我最终创建了自己的解决方案。以防万一它对其他人有用:
我们的想法是创建一个 promise 生成器数组,并将这个数组提供给一个辅助函数,该函数将一个接一个地执行 promise。
在我的例子中,辅助函数就是这样:
function promiseChain(chain) {
let output = new Promise((resolve, reject) => { resolve(); });
for (let i = 0; i < chain.length; i++) {
let f = chain[i];
output = output.then(f);
}
return output;
}
然后,例如,要依次加载多个网址,代码将是这样的:
// First build the array of promise generators:
let urls = [......];
let chain = [];
for (let i = 0; i < urls.length; i++) {
chain.push(() => {
return fetch(urls[i]);
});
}
// Then execute the promises one after another:
promiseChain(chain).then(() => {
console.info('All done');
});
这种方法的优点是它创建的代码相对接近于常规 for 循环,并且缩进最少。
我有类似的需求并尝试了可接受的答案,但我对操作顺序有疑问。 Promise.all
是解决方案。
function work(context) {
return new Promise((resolve, reject) => {
operation(context)
.then(result => resolve(result)
.catch(err => reject(err));
});
}
Promise
.all(arrayOfContext.map(context => work(context)))
.then(results => console.log(results))
.catch(err => console.error(err));