发布于 2026-01-06 3 阅读
0

了解 Javascript 中的 Async/await AWS AI LIVE!

理解 JavaScript 中的 Async/await

AWS AI 直播!

本文将探讨async/await每位 JavaScript 开发者进行异步编程的必备工具。如果您是 JavaScript 新手,也无需担心,本文将async/await从零开始,帮助您理解相关知识。

介绍

async/await是 JavaScript 中的一种模式,它使你的代码以同步方式执行,但又不影响 JavaScript 的异步行为。

定义异步函数

要定义一个异步函数,你只需要在函数定义前加上 async 关键字即可。

// async function always returns a promise
async function greet() {
  return "hello";
}
Enter fullscreen mode Exit fullscreen mode

简单得很!😎。在函数名前使用async关键字。

  • 使该函数返回一个 Promise。

  • 当函数返回时,问题得到解决。

  • 当抛出错误时,最终会拒绝。

这意味着你不需要每次创建 Promise 时都声明return Promise.new() 。

为了证明异步函数返回的是一个 Promise,我们可以快速添加一个then代码块来打印它的值。

async function greet() {
  return "Hello from an async function"
}
greet().then(message => console.log(message));
//Hello from an async function
Enter fullscreen mode Exit fullscreen mode

使用 await 和执行异步函数

能够在函数上执行then()`and`操作是不是很酷?但这并非异步函数的真正威力,函数的真正潜力在于语句。catch()asyncasyncawait

await使函数以同步方式执行,同时保持该行的控制权,直到等待的方法执行完毕。

async function greet() {
  return "Hello from an async function"
}

async function execute() {
  const message = await greet();
  console.log(message)
}
Enter fullscreen mode Exit fullscreen mode

以下是一些我们需要记住的经验法则。

👉 await 只能在 async 函数内部使用。

async如果在函数内部使用await ,则必须声明该函数;反之则不必。

这么说吧,如果await在方法内部使用语句,那么该方法必须是一个async方法,否则编译器会报错。

async function greet() {
  return "Hello from an async function";
}

function execute() {//this function must be async
  const message = await greet();
  console.log(message)
}
/* 
SyntaxError: await is only valid in async function
*/
~~~{% endraw %}

But declaring a function {% raw %}`async`{% endraw %} doesn't necessarily mean we would always use an {% raw %}`await` inside it. Here `greet()` is an `async` method but we don't have any {% raw %}`await`{% endraw %} statements inside it.


 👉 *await makes sense when the function it is called on, returns a promise or is an async function*{% raw %}


~~~javascript
//not an async function
function greet() {
 return "Hello from an async function";
}

async function execute() {
  const message = await greet();
  console.log(message); //Hello from an async function
}
~~~{% endraw %}

Although the code works exactly the same as the previous one, doing an {% raw %}`await`{% endraw %} on a {% raw %}`synchronous`{% endraw %} function does not make any sense. I would like to know what are your thoughts on this ?🤔🤔.

One important aspect of using await is the fact that it blocks the execution of the next lines of code until the await block is executed.
{% raw %}


```javascript
const asyncGreet = () => new Promise(resolve => setTimeout(resolve, 2000));

(async function execute() {
  console.log("before executing");
  await asyncGreet(); //blocks execution here
  // 👇 executed once await is finished
  console.log("I will be executed after 2000ms");
})();
```
{% endraw %}


Now you must be wondering if *await* makes the code synchronous, why should we use it? NodeJs or browser Javascript are single-threaded environments and execute one task at a time and widely used because of their asynchronous behaviour, which we're losing. So what is the point?

Yes, you're right that but if you observe in most of the cases, we need to perform a task in relation to others.
{% raw %}


```javascript
async function subscribeToNewsLetter() {
  const user  = await findUser(id);
  //👇methods need user email to execute
  await subscribe(user.email)
  await sendNotification(user.email)
}
```
{% endraw %}


That's correct. but what about code that is not related to each other? Well, there is an alternative for that as well i.e. (`Promise.all`).



```javascript
const asyncGreet = (name) =>  new Promise((resolve) => setTimeout(resolve(`Hello ${name}`), 2000));

const names = ['john', 'jane', 'david'];

(async function() {
  const greetingPromises = names.map(name => asyncGreet(name));
  console.log(await Promise.all(greetingPromises));
})();

```


I know the above code is a contrived example, what is important here is that we are using the power of `Promise.all` to execute all the promises 

#### Handling Errors in `Async/Await`.

Dealing with errors is pretty easy with *async/await*, we can use our old friend the *try/catch* block for achieving this.



```javascript
async function subscribeToNewsLetter() {
  try {
    const user  = await findUser(id);
    await subscribe(user.email)
    await sendNotification(user.email)
  } catch(err) {
    //handle error
  }
}
```



There is also another version where we can attach a *catch* handler directly to the *await* block. I don't use it personally but you can give it a try if you want👍.
{% raw %}


```javascript
  await asyncGreet().catch(err => console.log(err);
```
{% endraw %}

####  2x Readability, Easy Debugging

The following code uses a *Promise* to find the user by *id*, assigns the profile information, and then finds the user's subscription.



```javascript
function getUser(id, profile) {
  return new Promise((resolve, reject) => {
    User
      .find(id)
      .then((user) => {
        if(_.isEmpty(user)) return {};
        user.profile = profile;
        return user;
      })
      .then((user) => Subscription.find(user.id))
      .then(subscription => {
        if(_.isEmpty(subscription)) {
          user.subscription = null;
        } else {
          user.subscription = subscription;
        }
        return resolve(user)
      })
      .catch(err => reject(err))
  })
}
```



The above code works perfectly fine, but we could definitely make it more readable, concise, and easier to debug with `async`/`await`. Let's give it a go.
{% raw %}



```javascript
async function getUser(id, profile) {
  try {
    const user = await User.find(id);
    if(_.isEmpty(user)) return {};
    user.profile = profile;
    const subscription = await Subscription.find(user.id);
    user.subscription = subscription
    return user;
  } catch(err) {
    console.log(err);
  }
}
```
{% endraw %}



#### Callbacks and {% raw %}`Async/Await`{% endraw %} are Enemies

As we already saw in our previous example, promises play really well with {% raw %}`async`{% endraw %}/`await`. Any function that returns a promise can be used with `await` statement.

But when it comes to callbacks, its totally the opposite, callbacks cant be directly used with {% raw %}`async`{% endraw %}/`await`, they must be converted to a promise.


let's consider the following function which asynchronously tests if a value is even or not(raise an error).



```javascript
function asyncEven(id, cb){
  setTimeout(() => {
    const even = id%2 === 0;
    if (even) return cb(null, "even");
    else return cb("not even");
  }, 2000);
}
```


We know await is not permissible on callback but still, let's give it a try.
{% raw %}


```javascript
(async function() {
  //🐶👹 Wrong way
  const even = await asyncEven(2);
  console.log("isEven ", even); //undefined
})();

```
{% endraw %}

You must be thinking, that we didn't attach a callback that's the reason it printed {% raw %}`undefined`{% endraw %}.

Let's attach a callback, which is super weird but let's have patience.
{% raw %}

```javascript
(async function() {
  //this is also wrong 🐶👹
  const even = await asyncEven(2, (err, data) => { console.log("inside await on callback", err, data)});
  console.log("isEven ", even);
})();
/*
output:
even  undefined
inside await on callback even null
*/ 
```
{% endraw %}

It seems like the callback was called and we also got values from the asyncEven function. That's correct but still, it is a wrong approach.

`await` has no impact on callback. it similar to doing an await on a synchronous function.

Then why did it return *undefined*? That's a good question. This the default nature of asynchronous programming. The *setTimeout* function is a callback that returns a value via the callback after 2000ms, meanwhile, the control start executing the next line of code, and it reaches the end of the function, that is why we get an *undefined*.

So what is the solution? Pretty simple. Turn the {% raw %}`asyncEven`{% endraw %} function to a promise and use {% raw %}`await`{% endraw %} like a champ.
{% raw %}


```javascript
function asyncEven(id,) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const even = id%2 === 0;
      if (even) return resolve("even");
      else return reject("not even");
    }, 2000);
  })
}

(async function() {
  // waits for the execution
  const even = await asyncEven(2);
  console.log("iseven ", even);
})();

```
{% endraw %}

#### ForEach Does Not Play Well with {% raw %}`Async/Await`{% endraw %}

ForEach loop may have side effects if we use it with {% raw %}`async/await`{% endraw %}. Consider the following example, the {% raw %}`console.log`{% endraw %} statement here doesn't wait for the `await greet(name)`.



```javascript
async function greet(name) {
 return Promise.resolve(`Hello ${name}, how are you ?`);
}

(function() {
  console.log("before printing names");
  const names = ['john', 'jane', 'joe'];
  names.forEach(async (name) => {
   //does not wait here
    console.log(await greet(name));
  });
  console.log("after printing names");
})();
/*
before printing names
after printing names
Hello john, how are you ?
Hello jane, how are you ?
Hello joe, how are you ?
*/
```




#### More Than Just a Syntactic Sugar

So far we only know that `async/await` makes our code more readable, debug friendly and some people say it's a syntactic sugar on javascript promises. In reality, it's more than just a syntactic sugar.



```javascript
// promise
async1()
.then(x => asyncTwo(x))
.then(y => asyncThree(y))
//other statement
console.log("hello")


//async await
x = await async1();
y = await asyncTwo(x);
await asyncThree(y);

```



`await` suspends the execution of current function, while promise continues executing the current function adding the value to the `then()`. There is a significant difference between these two way of executing programs.

Let me explain, consider the promise version, if *asyncTwo()* or *asyncThree()* throws an async error while performing a task, will it include `async1()`in the stack trace ?

Here promise does not suspend the execution of current function, by the time `asyncTwo` resolves or rejects, the context is out of the promise statement. So ideally, it not able to include `asyncOne` in the stack trace . But thanks to V8 engine, it does some magic here, by keeping reference to `asyncOne()` ahead of the time in order to include `asyncOne()` in the context. But this does not come for free. Capturing the stack trace takes time (i.e. degrades performance); storing these stack traces requires memory.

This is where `async/await` beats promises in terms of performance, as the execution of current function is halted until the awaiting function is finished, so we already a have a reference to the function. 

------
*Thanks for reading this article, I hope this post was helpful in understanding the async/await feature of javascript. If you like my article, please show your love by liking this post, this would mean so much to me. Meanwhile you can check out my [article] (https://dev.to/saroj990/mastering-javascript-promises-4kfh) on javascript promises.*

References:
[https://mathiasbynens.be/notes/async-stack-traces](https://mathiasbynens.be/notes/async-stack-traces)
Enter fullscreen mode Exit fullscreen mode
文章来源:https://dev.to/saroj990/async-await-a-cure-for-promise-hell-52i5