promises
Promises are perhaps the most complicated and frustrating thing in the Javascript world (not accounting for Javascript itself). While the concept is actually not that super hard to grasp, asynchronous stuff can make even senior programmers recite their forbidden vocabulary from third grade.
承诺可能是Java语言世界中最复杂和令人沮丧的事情(不考虑Java语言本身)。 虽然这个概念实际上并不是那么难掌握,但是异步的东西甚至可以使高级程序员背诵三年级的禁忌词汇。
The most difficult challenges when working with Promises are the following:
使用Promises时最困难的挑战如下:
- How can I execute a series of Promises, then get their cumulative result when they’re all finished? 如何执行一系列的Promises,然后在它们全部完成后获得它们的累积结果?
- How can I execute a series of Promises in a lazy sequence, meaning one will only start running when the previous one has been fully completed? 如何以惰性顺序执行一系列Promises,这意味着只有在上一个Promise完全完成后才开始运行?
Both are possible, although the latter isn’t what Promises are generally designed for. But in a situation with potentially thousands of Promises at hand (for example when you have to go through a list of URL’s and save the responses locally) parallel processing may not be a very good idea.
两者都是可能的,尽管后者不是Promises通常设计的目的。 但是在手头可能有成千上万个Promise的情况下(例如,当您必须遍历URL列表并在本地保存响应时),并行处理可能不是一个好主意。
Let’s build a simple Promise
让我们建立一个简单的Promise
Perhaps the most basic form of a Promise
, frequently used to illustrate the concept in tutorials, is waiting for asetTimeout()
to complete:
Promise
最基本形式(经常用于说明教程中的概念)也许正在等待setTimeout()
完成:
const delay = new Promise(resolve => {
setTimeout(() => {
resolve();
}, 5000);
}).then(() => {
console.log('Done waiting.');
});
Let’s have an array of numbers, each indicating a number of milliseconds to wait. We’re going to call the above timeout function with each.
让我们有一个数字数组,每个数字表示要等待的毫秒数。 我们将分别调用上述超时功能。
const delays = [1000, 2000, 5000, 3000, 500, 12000];
First things first, let’s update our Promise
to accept a parameter for the milliseconds value. We’re going to have to turn it into a function.
首先,让我们更新Promise
以接受毫秒值的参数。 我们将不得不把它变成一个函数。
const delay = (milliseconds) => { return new Promise(resolve => {
setTimeout(() => {
resolve(milliseconds);
}, milliseconds);
});
}delay(12000)
.then(milliseconds => {
console.log(`Done waiting ${milliseconds / 1000} seconds.`);
});
What has changed? We wrapped the Promise
into a function. This is how we can pass a parameter to it. We also changed the resolve()
call to return the milliseconds value, so our .then()
branch can tell us how much waiting has just been completed.
有什么变化? 我们将Promise
包装成一个函数。 这就是我们可以向其传递参数的方式。 我们还更改了resolve()
调用以返回毫秒值,因此我们的.then()
分支可以告诉我们刚刚完成了多少等待。
The above code calls delay
with a parameter of 12,000. Our script will wait 12,000 milliseconds, then say:
上面的代码调用参数为12,000的delay
。 我们的脚本将等待12,000毫秒,然后说:
Done waiting 12 seconds.
Previously we defined delay
as a Promise
. Now it`s a function
, but as it’s returning a Promise
, we can still treat it as a Promise
.
之前,我们将delay
定义为Promise
。 现在它是一个function
,但是当它返回Promise
,我们仍然可以将其视为Promise
。
Enter the array
输入数组
Remember, we have this little array lying around:
记住,我们周围有这个小数组:
const delays = [1000, 2000, 5000, 3000, 500, 12000];
We want to call the delay
function with each value, and we expect our script to wait 1 second, then 2 seconds, then 5, 3, 0.5 and finally 12. All together it’ll make 23.5 seconds.
我们希望使用每个值调用delay
函数,并且我们希望脚本等待1秒,然后等待2秒,然后等待5、3、0.5,最后等待12。总共将花费23.5秒。
Our promise chain will be like a fireworks show. We don’t want all of it to go up at once. (As a matter of fact, this is what happened in San Diego in 2012, and it was very spectacular, but it’s still not what we want to happen.)
我们的诺言链就像烟花表演。 我们不希望所有这些都立即上升。 (事实上,这就是2012年在圣地亚哥发生的事情, 虽然非常壮观 ,但这仍然不是我们想要发生的事情。)
Batch execution or a promise chain is possible with the Promise.all()
method. It’s a handy little function which allows you to pass an array of Promise
objects to have them executed and give you a common .then()
and .catch()
handler. All you have to do is to collect your calls into an array.
使用Promise.all()
方法可以执行批处理或答应链。 这是一个方便的小功能,让你通过数组Promise
对象,让他们执行的,并给你一个共同的.then()
和.catch()
处理程序。 您要做的就是将您的呼叫收集到一个数组中。
How? With the array.map()
method.
怎么样? 使用array.map()
方法。
Promise.all(
delays.map(d => delay(d))
)
.then(() => console.log('Waited enough!'));
Now we can detect when all of our Promises finished running.
现在我们可以检测到所有Promises何时完成运行。
However, this will still trigger them all at once. Our script will actually only wait 12 seconds, as the largest value in our array is 12,000, and by the time it finishes, all others have finished too. So our fireworks went up all at once.
但是,这仍然会立即触发它们。 我们的脚本实际上只等待12秒,因为数组中的最大值是12,000,到脚本完成时,所有其他脚本也都完成了。 因此,我们的烟火立即全部上升。
This may not actually be a problem in practice. Javascript engines are smart enough to notice there’s a queue building up, and although they’ll still accept your promises, they don’t actually run more than resources will allow. So if you pass ten thousand URL requests, not all of them will run instantly. However, if your promise chain is long enough, it may cause a TooManyElementsInPromiseAll
exception. According to V8 unit tests, it needs 2²¹ or 2097151 concurrent promises.
实际上,这实际上不是问题。 Javascript引擎足够聪明,可以注意到正在建立一个队列,尽管它们仍会接受您的承诺,但实际上运行的资源不会超出资源的允许范围。 因此,如果您传递一万个URL请求,并非所有请求都会立即运行。 但是,如果您的承诺链足够长,则可能会导致TooManyElementsInPromiseAll
异常。 根据V8单元测试,它需要2²¹或2097151并发承诺。
Here just let me quickly mention that Promise.all()
will go to its .catch()
handler if just one Promise
in the chain has failed. You may consider to use Promise.allSettled()
instead, which will generate a neat JSON in your .then()
branch telling you which input value finished the Promise
successfully and which didn’t. Attention: it doesn’t seem to work in TypeScript 3.9.7.
在这里,让我快速提及一下,如果链中只有一个Promise
失败, Promise.all()
将进入其.catch()
处理程序。 您可以考虑改用Promise.allSettled()
,它将在您的.then()
分支中生成纯净的JSON,告诉您哪些输入值成功完成了Promise
而哪些没有成功完成。 注意:它在TypeScript 3.9.7中似乎不起作用。
[
{ status: 'fulfilled', value: 1000 },
{ status: 'rejected', reason: '2000, I hate this number' },
{ status: 'fulfilled', value: 5000 },
{ status: 'fulfilled', value: 3000 },
{ status: 'fulfilled', value: 500 },
{ status: 'rejected', reason: '2000, I hate this number' }
]
True sequential execution
真正的顺序执行
But really, how can we make the loop execute the Promises one by one, instead of all at once?
但是,实际上,如何使循环一个接一个地而不是一次地执行Promises?
One way is to do it manually:
一种方法是手动进行:
delay(delays[0])
.then(() => delay(delays[1]))
.then(() => delay(delays[2]))
.then(() => delay(delays[3]))
.then(() => delay(delays[4]))
.then(() => delay(delays[5]))
.then(() => console.log('Done!'));
This works, but isn’t what we want to do, except perhaps when we have a hardcoded set of values, and we also don’t care about our tech lead’s facial expression. Unfortunately, Promise
is fully asynchronous and can’t be made to act synchronously. Except, of course, if we don’t mind some hacking! The solution is to create a recursive function. Here’s the full code:
这可行,但不是我们想要做的,除非当我们有一组硬编码的值时,而且我们也不在乎技术主管的面部表情。 不幸的是, Promise
是完全异步的,不能使其同步动作。 当然,除非我们不介意一些黑客行为! 解决方案是创建一个递归函数。 这是完整的代码:
const delay = (milliseconds) => {
console.log(`Waiting: ${milliseconds / 1000} seconds.`);return new Promise((resolve) => {
setTimeout(() => {
resolve(milliseconds);
}, milliseconds);
});
}const delays = [1000, 2000, 5000, 3000, 500, 12000];
const startTime = Date.now();const doNextPromise = (d) => { delay(delays[d]) .then(x => {
console.log(`Waited: ${x / 1000} seconds\n`);
d++; if (d < delays.length)
doNextPromise(d)
else
console.log(`Total: ${(Date.now() - startTime) / 1000} seconds.`); })
}doNextPromise(0);
Our delay()
function hasn’t changed, and neither has our delays
array. The magic begins at the doNextPromise()
function. It’s a recursive function that takes a number and calls the delay()
function, passing delays[d]
to it. Then in the.then()
branch we increment d
and call doNextPromise()
again, until d
is smaller than the last index of thedelays
array:delays.length-1
.
我们的delay()
函数没有改变,而且我们的delays
数组也没有改变。 魔术始于doNextPromise()
函数。 这是一个递归函数,它接受一个数字并调用delay()
函数,并将delays[d]
传递给它。 然后在.then()
分支中,我们增加d
并再次调用doNextPromise()
,直到d
小于delays
数组的最后一个索引: delays.length-1
。
When d
is larger than the last index, we tell the user how much time has passed since we started the loop.
当d
大于最后一个索引时,我们告诉用户自开始循环以来经过了多少时间。
Run this script, and you’ll see something like this:
运行此脚本,您将看到类似以下内容:
Waiting: 1 seconds.
Waited: 1 secondsWaiting: 2 seconds.
Waited: 2 secondsWaiting: 5 seconds.
Waited: 5 secondsWaiting: 3 seconds.
Waited: 3 secondsWaiting: 0.5 seconds.
Waited: 0.5 secondsWaiting: 12 seconds.
Waited: 12 secondsThis loop took 23 seconds.
Good luck untangling that Promise
mess you’re struggling with!
祝您在挣扎中解决Promise
混乱时好运!
翻译自: https://medium.com/developer-rants/running-promises-in-a-loop-sequentially-one-by-one-bd803181b283
promises