Ever had to deal with JS code that just… didn’t run the way you expected it to? Maybe it seemed like functions got executed at random, unpredictable times, or the execution got delayed. There’s a chance you were dealing with a cool new feature that ES6 introduced: Promises!
My curiosity from many years ago has paid off and my sleepless nights have once again given me the time to make some animations. Time to talk about Promises: why would you use them, how do they work “under the hood”, and how can we write them in the most modern way?
If you haven’t read my previous post on the JavaScript Event Loop yet, it may be useful to read that first! I’ll be covering the event loop again assuming some basic knowledge about the call stack, Web API and the queue, but this time we’ll also be covering some exciting extra features 🤩
If you’re already somewhat familiar with promises, here are some shortcuts to save you some precious scrolling time.
Introduction
When writing JavaScript, we often have to deal with tasks that rely on other tasks! Let’s say that we want to get an image, compress it, apply a filter, and save it 📸
在编写 JavaScript 时,我们经常要处理依赖其他任务的任务!假设我们想要获取一个图像,压缩它,应用过滤器,然后保存它 📸
The very first thing we need to do, is get the image that we want to edit. A getImage
function can take care of this! Only once that image has been loaded successfully, we can pass that value to a resizeImage
compressImage
function. When the image has been resized successfully, we want to apply a filter to the image in the applyFilter
function. After the image has been compressed and we’ve added a filter, we want to save the image and let the user know that everything worked correctly! 🥳
我们需要做的第一件事就是 获取 我们想要编辑的图像。
getImage
函数可以解决这个问题!只有成功加载该图像后,我们才能将该值传递给compressImage
函数。成功调整图像大小后,我们希望在applyFilter
函数中对图像应用过滤器。在图像被压缩并添加过滤器后,我们想在saveImage
函数保存图像并让用户知道一切正常! 🥳
In the end, we’ll end up with something like this:
Hmm… Notice anything here? Although it’s… fine, it’s not great. We end up with many nested callback functions that are dependent on the previous callback function. This is often referred to as a callback hell, as we end up with tons of nested callback functions that make the code quite difficult to read!
嗯…… 注意到这里有什么吗?虽然…… 还好,但不是很好。我们最终得到了许多依赖于前一个回调函数的嵌套回调函数。这通常被称为回调地狱,因为我们最终会得到大量嵌套的回调函数,这使得代码很难阅读!
Luckily, we now got something called promises to help us out! Let’s take a look at what promises are, and how they can help us in situations like these! 😃
幸运的是,我们现在有了一个叫做 promises 的东西来帮助我们!让我们来看看 Promise 是什么,以及它们如何在这样的情况下帮助我们! 😃
Promise Syntax
ES6 introduced Promises. In many tutorials, you’ll read something like:
“A promise is a placeholder for a value that can either resolve or reject at some time in the future”
Yeah… That explanation never made things clearer for me. In fact it only made me feel like a Promise was a weird, vague, unpredictable piece of magic. So let’s look at what promises really are.
We can create a promise, using a Promise
constructor that receives a callback. Okay cool, let’s try it out!
我们可以创建一个 promise,使用一个接收回调的 Promise 构造函数。不错,快来试试吧!
new Promise(() => {});
// Promise {<pending>}
// __proto__: Promise
// [[PromiseState]]: "pending"
// [[PromiseResult]]: undefined
Wait woah, what just got returned?
A Promise
is an object that contains a status, ([[PromiseStatus]]
) and a value ([[PromiseValue]]
). In the above example, you can see that the value of [[PromiseStatus]]
is "pending"
, and the value of the promise is undefined
.
Promise
是一个包含状态 ([[PromiseStatus]]
) 和值 ([[PromiseValue]]
) 的对象。在上面的例子中,可以看到[[PromiseStatus]]
的值为"pending"
,promise 的值为undefined
。
Don’t worry - you’ll never have to interact with this object, you can’t even access the [[PromiseStatus]]
and [[PromiseValue]]
properties! However, the values of these properties are important when working with promises.
别担心 - 你永远不必与这个对象打交道,你甚至不能访问
[[PromiseStatus]]
和[[PromiseValue]]
属性!但是,在使用 Promise 时,这些属性的值很重要。
The value of the PromiseStatus
, the state, can be one of three values:
- ✅
fulfilled
: The promise has beenresolved
. Everything went fine, no errors occurred within the promise 🥳 - ❌
rejected
: The promise has beenrejected
. Argh, something went wrong.. - ⏳
pending
: The promise has neither resolved nor rejected (yet), the promise is stillpending
.
Alright this all sounds great, but when is a promise status "pending"
, "fulfilled"
or "rejected"
? And why does that status even matter?
好吧,这一切听起来都不错,但是承诺状态何时是 “待定”、“已实现” 或 “已拒绝”?为什么这种状态很重要?
In the above example, we just passed the simple callback function () => {}
to the Promise
constructor. However, this callback function actually receives two arguments. The value of the first argument, often called resolve
or res
, is the method to be called when the Promise should resolve. The value of the second argument, often called reject
or rej
, is the value method to be called when the Promise should reject, something went wrong.
在上面的例子中,我们只是将简单的回调函数
() => {}
传递给了Promise
构造函数。然而,这个回调函数实际上接收两个参数。第一个参数的值,通常称为resolve
或res
,是 Promise 应该解析时要调用的方法。第二个参数的值,通常称为reject
或rej
,是当 Promise 应该拒绝时调用的值方法,出现问题。
Let’s try and see that gets logged when we invoke either the resolve
or reject
method! In my example, I called the resolve
method res
, and the reject
method rej
.
让我们试着看看当我们调用
resolve
或reject
方法时会被记录下来!在我的示例中,我调用了解析方法res
和拒绝方法rej
。
Awesome! We finally know how to get rid of the "pending"
status and the undefined
value! The status of a promise is "fulfilled"
if we invoked the resolve
method, and the status of the promise is "rejected
“ if we invoked the rejected
method.
惊人的!我们终于知道如何摆脱
"pending"
状态和undefined
值了!如果我们调用了resolve
方法,promise 的状态就是"fulfilled"
,如果我们调用了rejected
方法,promise 的状态就是"rejected
“。
The value of a promise, the value of [[PromiseValue]]
, is the value that we pass to the either the resolved
or rejected
method as their argument.
一个 promise 的值,即
[[PromiseValue]]
的值,是我们作为参数传递给resolved
或rejected
方法的值。
Fun fact, I let Jake Archibald proofread this article and he actually pointed out there’s a bug in Chrome that currently shows the status as
"resolved"
instead of"fulfilled"
. Thanks to Mathias Bynens it’s now fixed in Canary! 🥳🕺🏼Chrome and Safari call this a “resolved” promise, which is true, but kinda misleading…
Okay so, now we know a little bit better how to control that vague Promise
object. But what is it used for?
好的,现在我们对如何控制那个模糊的
Promise
对象有了更好的了解。但是它有什么用呢?
In the introductory section, I showed an example in which we get an image, compress it, apply a filer, and save it! Eventually, this ended up being a nested callback mess.
在介绍部分,我展示了一个示例,其中我们获取图像、压缩它、应用文件管理器并保存它!最终,这最终成为一个嵌套的回调混乱。
Luckily, Promises can help us fix this! First, let’s rewrite the entire code block, so that each function returns a Promise
instead.
幸运的是,Promises 可以帮助我们解决这个问题!首先,让我们重写整个代码块,让每个函数都返回一个 Promise。
If the image is loaded and everything went fine, let’s resolve the promise with the loaded image! Else, if there was an error somewhere while loading the file, let’s reject the promise with the error that occurred.
如果图像已加载并且一切正常,让我们用加载的图像解决 promise!否则,如果在加载文件时某处出现错误,让我们拒绝发生错误的承诺。
function getImage(file) {
return new Promise((rs, rej) => {
try {
const data = readFile(file);
resolve(data);
} catch (err) {
reject(new Error(err));
}
});
}
Let’s see what happens when we run this in the terminal!
Cool! A promise got returned with the value of the parsed data, just like we expected.
凉爽的!正如我们预期的那样,一个带有解析数据值的承诺被返回。
But… what now? We don’t care about that entire promise object, we only care about the value of the data! Luckily, there are built-in methods to get a promise’s value. To a promise, we can attach 3 methods:
但是……现在怎么办?我们不关心整个 promise 对象,我们只关心数据的价值!幸运的是,有一些内置方法可以获取 Promise 的值。对于 promise,我们可以附加 3 个方法:
.then()
: Gets called after a promise resolved..catch()
: Gets called after a promise rejected..finally()
: Always gets called, whether the promise resolved or rejected.
The .then
method receives the value passed to the resolve
method.
The .catch
method receives the value passed to the rejected
method
Finally, we have the value that got resolved by the promise without having that entire promise object! We can now do whatever we want with this value.
最后,我们有了由 Promise 解析的值,而没有整个 Promise 对象!我们现在可以用这个值做任何我们想做的事情。
FYI, when you know that a promise will always resolve or always reject, you can write Promise.resolve
or Promise.reject
, with the value you want to reject or resolve the promise with!
仅供参考,当您知道承诺将始终解决或始终拒绝时,您可以编写
Promise.resolve
或Promise.reject
,并使用您想要拒绝或解决承诺的值!
You’ll often see this syntax in the following examples 😄
In the getImage
example, we ended up having to nest multiple callbacks in order to run them. Luckily, the .then
handlers can help us with that! 🥳
在
getImage
示例中,我们最终不得不嵌套多个回调以运行它们。幸运的是,.then
处理程序可以帮助我们解决这个问题! 🥳
The result of the .then
itself is a promise value. This means that we can chain as many .then
s as we want: the result of the previous then
callback will be passed as an argument to the next then
callback!
.then
本身的结果是一个 promise 值。这意味着我们可以根据需要链接任意数量的.then
:前一个 then 回调的结果将作为参数传递给下一个then
回调!
In the case of the getImage
example, we can chain multiple then
callbacks in order to pass the processed image onto the next function! Instead of ending up with many nested callbacks, we get a clean then
chain.
在
getImage
示例中,我们可以链接多个then
回调,以便将处理后的图像传递给下一个函数!我们得到了一个干净的then
链,而不是以许多嵌套的回调结束。
Perfect! This syntax already looks way better than the nested callbacks.
完美的!这种语法看起来比嵌套回调更好。
Microtasks and (Macro)tasks
微任务和(宏)任务
Okay so we know a little better how to create a promise and how to extract values out of a promise. Let’s add some more code to the script, and run it again:
好的,所以我们对如何创建承诺以及如何从承诺中提取值有了更好的了解。让我们向脚本添加更多代码,然后再次运行它:
Wait what?! 🤯
First, Start!
got logged. Okay we could’ve seen that one coming: console.log('Start!')
is on the very first line! However, the second value that got logged was End!
, and not the value of the resolved promise! Only after End!
was logged, the value of the promise got logged. What’s going on here?
首先,
Start!
被登录了。好吧,我们已经看到了:console.log('Start!')
在第一行!但是,记录的第二个值是End!
,而不是已解决的 Promise 的值!只有End!
被记录,promise 的值被记录。这里发生了什么?
We’ve finally seen the true power of promises! 🚀 Although JavaScript is single-threaded, we can add asynchronous behavior using a Promise
!
我们终于看到了 Promise 的真正力量! 🚀 虽然 JavaScript 是单线程的,但我们可以使用 Promise 添加异步行为!
But wait, haven’t we seen that before? 🤔 In the JavaScript event loop, can’t we also use methods native to the browser such as setTimeout
to create some sort of asynchronous behavior?
但是等等,我们以前没见过吗? 🤔 在 JavaScript 事件循环中,我们不能也使用浏览器原生的方法,例如
setTimeout
来创建某种异步行为吗?
Yes! However, within the Event Loop, there are actually two types of queues: the (macro)task queue (or just called the task queue), and the microtask queue. The (macro)task queue is for (macro)tasks and the microtask queue is for microtasks.
是的!然而,在事件循环内部,实际上有两种类型的队列:(宏)任务队列(或简称为任务队列)和微任务队列。 (宏)任务队列用于(宏)任务,微任务队列用于微任务。
So what’s a (macro)task and what’s a microtask? Although there are a few more than I’ll cover here, the most common are shown in the table below!
那么什么是(宏)任务,什么是微任务?虽然有一些比我在这里介绍的要多,但最常见的如下表所示!
(Macro)task | setTimeout |
setInterval |
setImmediate |
Microtask | process.nextTick |
Promise callback |
queueMicrotask |
Ahh, we see Promise
in the microtask list! 😃 When a Promise
resolves and calls its then()
, catch()
or finally()
, method, the callback within the method gets added to the microtask queue! This means that the callback within the then()
, catch()
or finally()
method isn’t executed immediately, essentially adding some async behavior to our JavaScript code!
啊,我们在微任务列表中看到了 Promise! 😃 当 Promise 解析并调用其
then()
、catch()
或finally()
方法时,该方法中的回调会被添加到微任务队列中!这意味着then()
、catch()
或finally()
方法中的回调不会立即执行,实质上是向我们的 JavaScript 代码添加了一些异步行为!
So when is a then()
, catch()
or finally()
callback executed? The event loop gives a different priority to the tasks:
那么什么时候执行
then()
、catch()
或finally()
回调?事件循环为任务赋予不同的优先级:
- All functions in that are currently in the call stack get executed. When they returned a value, they get popped off the stack.
- When the call stack is empty, all queued up microtasks are popped onto the callstack one by one, and get executed! (Microtasks themselves can also return new microtasks, effectively creating an infinite microtask loop 😬)
- If both the call stack and microtask queue are empty, the event loop checks if there are tasks left on the (macro)task queue. The tasks get popped onto the callstack, executed, and popped off!
- 当前在调用堆栈中的所有函数都将执行. 当它们返回一个值时, 它们就会从堆栈中弹出.
- 当调用堆栈为空时, 所有排队的微任务都会一一弹出到调用堆栈上, 然后执行!(微任务本身也可以返回新的微任务, 从而有效地创建无限的微任务循环 loop)
- 如果调用堆栈和微任务队列都为空, 则事件循环将检查 (宏) 任务队列上是否还有任务. 任务被弹出到调用堆栈上, 执行并弹出!
Let’s take a look at a quick example, simply using:
Task1
: a function that’s added to the call stack immediately, for example by invoking it instantly in our code.Task2
,Task3
,Task4
: microtasks, for example a promisethen
callback, or a task added withqueueMicrotask
.Task5
,Task6
: a (macro)task, for example asetTimeout
orsetImmediate
callback
First, Task1
returned a value and got popped off the call stack. Then, the engine checked for tasks queued in the microtask queue. Once all the tasks were put on the call stack and eventually popped off, the engine checked for tasks on the (macro)task queue, which got popped onto the call stack, and popped off when they returned a value.
首先,
Task1
返回一个值并从调用堆栈中弹出。然后,引擎检查在微任务队列中排队的任务。一旦所有任务都被放入调用堆栈并最终弹出,引擎就会检查(宏)任务队列中的任务,这些任务被弹出到调用堆栈中,并在它们返回值时弹出。
Okay okay enough pink boxes. Let’s use it with some real code!
console.log('Start!');
setTimeout(() => {
console.log('Timeout!');
}, 0);
Promise.resolve('Promise!').then((res) => console.log(res));
console.log('End!');
//> "Start!"
//> "End!"
//> "Promise!"
//> "Timeout!"
In this code, we have the macro task setTimeout
, and the microtask promise then()
callback. Once the engine reaches the line of the setTimeout
function. Let’s run this code step-by-step, and see what gets logged!
在这段代码中,我们有宏任务
setTimeout
和微任务 promisethen()
回调。一旦引擎到达setTimeout
函数的行。让我们一步一步地运行这段代码,看看记录了什么!
Quick FYI - in the following examples I’m showing methods like
console.log
,setTimeout
andPromise.resolve
being added to the call stack. They’re internal methods and actually don’t appear in stack traces - so don’t worry if you’re using the debugger and you don’t see them anywhere! It just makes explaining this concept easier without adding a bunch of boilerplate code 🙂
快速参考 - 在以下示例中,我将展示诸如
console.log
、setTimeout
和Promise.resolve
之类的方法被添加到调用堆栈中。它们是内部方法,实际上不会出现在堆栈跟踪中 - 所以如果您正在使用调试器并且在任何地方都看不到它们,请不要担心!它只是使解释这个概念更容易,而无需添加一堆样板代码 🙂
On the first line, the engine encounters the console.log()
method. It gets added to the call stack, after which it logs the value Start!
to the console. The method gets popped off the call stack, and the engine continues.
在第一行,引擎遇到了
console.log()
方法。它被添加到调用堆栈中,之后它会记录值Start!
到控制台。该方法从调用堆栈中弹出,引擎继续。
The engine encounters the setTimeout
method, which gets popped on to the call stack. The setTimeout
method is native to the browser: its callback function (() => console.log('In timeout')
) will get added to the Web API, until the timer is done. Although we provided the value 0
for the timer, the call back still gets pushed to the Web API first, after which it gets added to the (macro)task queue: setTimeout
is a macro task!
引擎遇到
setTimeout
方法,该方法被弹出到调用堆栈中。setTimeout
方法是浏览器本机的:它的回调函数(() => console.log('In timeout'))
将被添加到 Web API,直到计时器完成。尽管我们为计时器提供了值0
,但回调仍然首先被推送到 Web API,然后它会被添加到(宏)任务队列中:setTimeout
是一个宏任务!
The engine encounters the Promise.resolve()
method. The Promise.resolve()
method gets added to the call stack, after which is resolves with the value Promise!
. Its then
callback function gets added to the microtask queue.
引擎遇到了
Promise.resolve()
方法。Promise.resolve()
方法被添加到调用堆栈中,之后使用值Promise!
解析。它的then
回调函数被添加到微任务队列中。
The engine encounters the console.log()
method. It gets added to the call stack immediately, after which it logs the value End!
to the console, gets popped off the call stack, and the engine continues.
引擎遇到了
console.log()
方法。它会立即添加到调用堆栈中,然后记录值End!
到控制台,从调用堆栈中弹出,然后引擎继续。
The engine sees the callstack is empty now. Since the call stack is empty, it’s going to check whether there are queued tasks in the microtask queue! And yes there are, the promise then
callback is waiting for its turn! It gets popped onto the call stack, after which it logs the resolved value of the promise: the string Promise!
in this case.
引擎现在看到调用堆栈是空的。由于调用栈是空的,它会检查微任务队列中是否有排队的任务!是的,承诺
then
回调正在等待轮到它!它被弹出到调用堆栈中,之后它会记录承诺的解析值:在这种情况下是字符串Promise!
。
The engine sees the call stack is empty, so it’s going to check the microtask queue once again to see if tasks are queued. Nope, the microtask queue is all empty.
引擎看到调用堆栈是空的,所以它会再次检查微任务队列以查看任务是否在队列中。不,微任务队列全是空的。
It’s time to check the (macro)task queue: the setTimeout
callback is still waiting there! The setTimeout
callback gets popped on to the callstack. The callback function returns the console.log
method, which logs the string "In timeout!"
"Timeout!"
. The setTimeout
callback get popped off the callstack.
是时候检查(宏)任务队列了:
setTimeout
回调仍在那里等待!setTimeout
回调被弹出到调用堆栈。回调函数返回console.log
方法,该方法记录字符串"In timeout!"
"Timeout!"
。setTimeout
回调从调用堆栈中弹出。
Finally, all done! 🥳 It seems like the output we saw earlier wasn’t so unexpected after all.
最后,一切都完成了! 🥳 看来我们之前看到的输出毕竟不是那么出人意料。
Async/Await
ES7 introduced a new way to add async behavior in JavaScript and make working with promises easier! With the introduction of the async
and await
keywords, we can create async functions which implicitly return a promise. But.. how can we do that? 😮
ES7 引入了一种在 JavaScript 中添加异步行为的新方法,并使使用 Promise 更容易!通过引入
async
和await
关键字,我们可以创建隐式返回承诺的异步函数。但是..我们怎么能做到这一点? 😮
Previously, we saw that we can explicitly create promises using the Promise
object, whether it was by typing new Promise(() => {})
, Promise.resolve
, or Promise.reject
.
之前,我们看到我们可以使用 Promise 对象显式创建 Promise,无论是通过键入
new Promise(() => {})
、Promise.resolve
还是Promise.reject
。
Instead of explicitly using the Promise
object, we can now create asynchronous functions that implicitly return an object! This means that we no longer have to write any Promise
object ourselves.
我们现在可以创建隐式返回对象的异步函数,而不是显式使用 Promise 对象!这意味着我们不再需要自己编写任何 Promise 对象。
Promise.resolve('Hello!')
async funcion greet(){
return 'Hello!';
}
Although the fact that async functions implicitly return promises is pretty great, the real power of async
functions can be seen when using the await
keyword! With the await
keyword, we can suspend(暂停) the asynchronous function while we wait for the await
ed value return a resolved promise. If we want to get the value of this resolved promise, like we previously did with the then()
callback, we can assign variables to the await
ed promise value!
尽管 async 函数隐式返回 promise 的事实非常好,但是使用 await 关键字时可以看到 async 函数的真正威力!使用 await 关键字,我们可以暂停(暂停)异步函数,同时等待等待的值返回已解决的承诺。如果我们想获得这个已解析的 promise 的值,就像我们之前对 then() 回调所做的那样,我们可以为等待的 promise 值分配变量!
So, we can suspend an async function? Okay great but.. what does that even mean?
Let’s see what happens when we run the following block of code:
const one = () => Promise.resolve('One!');
async function myFunc() {
console.log('In Function!');
const res = await one();
console.log(res);
}
console.log('Before function!');
myFunc();
console.log('After function!');
"Before function!"
"In Function!"
"After function!"
"One!"
Hmm.. What’s happening here?
First, the engine encounters a console.log
. It gets popped onto the call stack, after which Before function!
gets logged.
首先,引擎遇到一个
console.log
。它被弹出到调用堆栈中,之后是Before function!
被记录。
Then, we invoke the async function myFunc()
, after which the function body of myFunc
runs. On the very first line within the function body, we call another console.log
, this time with the string In function!
. The console.log
gets added to the call stack, logs the value, and gets popped off.
然后,我们调用异步函数
myFunc()
,然后运行myFunc
的函数体。在函数体的第一行,我们调用另一个console.log
,这次使用字符串In function!
。console.log
被添加到调用堆栈中,记录值,然后弹出。
The function body keeps on being executed, which gets us to the second line. Finally, we see an await
keyword! 🎉
函数体继续执行,这让我们进入第二行。最后,我们看到了一个 await 关键字! 🎉
The first thing that happens is that the value that gets awaited gets executed: the function one
in this case. It gets popped onto the call stack, and eventually returns a resolved promise. Once the promise has resolved and one
returned a value, the engine encounters the await
keyword.
发生的第一件事是等待的值被执行:在这种情况下是函数
one
。它被弹出到调用堆栈中,并最终返回一个已解决的承诺。一旦 promise 得到解决并返回一个值,引擎就会遇到 await 关键字。
When encountering an await
keyword, the async
function gets suspended. ✋🏼 The execution of the function body gets paused, and the rest of the async function gets run in a microtask instead of a regular task!
遇到
await
关键字时,异步函数会暂停。 ✋🏼 函数体的执行被暂停,异步函数的其余部分在微任务而不是常规任务中运行!
Now that the async function myFunc
is suspended as it encountered the await
keyword, the engine jumps out of the async function and continues executing the code in the execution context in which the async function got called: the global execution context in this case! 🏃🏽♀️
现在异步函数
myFunc
在遇到 await 关键字时被挂起,引擎跳出异步函数并继续在调用异步函数的执行上下文中执行代码:在这种情况下为全局执行上下文! 🏃🏽♀️
Finally, there are no more tasks to run in the global execution context! The event loop checks to see if there are any microtasks queued up: and there are! The async myFunc
function is queued up after resolving the valued of one
. myFunc
gets popped back onto the call stack, and continues running where it previously left off(中断).
最后,在全局执行上下文中没有更多的任务要运行了!事件循环检查是否有任何微任务排队:确实有! async myFunc 函数在解析值 1 后排队。 myFunc 被弹回调用堆栈,并在它之前停止的地方继续运行(中断)。
The variable res
finally gets its value, namely the value of the resolved promise that one
returned! We invoke console.log
with the value of res
: the string One!
in this case. One!
gets logged to the console and gets popped off the call stack! 😊
变量 res 终于得到了它的值,即返回的已解决的 promise 的值!我们使用 res 的值调用 console.log:字符串 One!在这种情况下。一!登录到控制台并从调用堆栈中弹出! 😊
Finally, all done! Did you notice how async
functions are different compared to a promise then
? The await
keyword suspends the async
function, whereas the Promise body would’ve kept on being executed if we would’ve used then
!
最后,一切都完成了!您是否注意到异步函数与 promise 相比有何不同? await 关键字暂停了异步函数,而如果我们当时使用的话,Promise 主体将继续执行!
Hm that was quite a lot of information! 🤯 No worries at all if you still feel a bit overwhelmed when working with Promises, I personally feel that it just takes experience to notice patterns and feel confident when working with asynchronous JavaScript.
嗯,这是相当多的信息! 🤯 如果您在使用 Promises 时仍然感到有点不知所措,请完全不用担心,我个人认为只需要经验来注意模式并在使用异步 JavaScript 时感到自信。
However, I hope that the “unexpected” or “unpredictable” behavior that you might encounter when working with async JavaScript makes a bit more sense now!
但是,我希望您在使用异步 JavaScript 时可能遇到的“意外”或“不可预测”行为现在更有意义了!