VDone Demo VDone Demo
Home
  • Articles

    • JavaScript
  • Study Notes

    • JavaScript Tutorial
    • Professional JavaScript
    • ES6 Tutorial
    • Vue
    • React
    • TypeScript: Build Axios from Scratch
    • Git
    • TypeScript
    • JS Design Patterns
  • HTML
  • CSS
  • Technical Docs
  • GitHub Tips
  • Node.js
  • Blog Setup
  • Learning
  • Interviews
  • Miscellaneous
  • Practical Tips
  • Friends
About
Bookmarks
  • Categories
  • Tags
  • Archives
GitHub (opens new window)

Nikolay Tuzov

Backend Developer
Home
  • Articles

    • JavaScript
  • Study Notes

    • JavaScript Tutorial
    • Professional JavaScript
    • ES6 Tutorial
    • Vue
    • React
    • TypeScript: Build Axios from Scratch
    • Git
    • TypeScript
    • JS Design Patterns
  • HTML
  • CSS
  • Technical Docs
  • GitHub Tips
  • Node.js
  • Blog Setup
  • Learning
  • Interviews
  • Miscellaneous
  • Practical Tips
  • Friends
About
Bookmarks
  • Categories
  • Tags
  • Archives
GitHub (opens new window)
  • Introduction to ECMAScript 6
  • let and const Commands
  • Destructuring Assignment of Variables
  • String Extensions
  • New String Methods
  • Regular Expression Extensions
  • Number Extensions
  • Function Extensions
  • Array Extensions
  • Object Extensions
  • 对象的新增方法
  • Symbol
  • Set 和 Map 数据结构
  • Proxy
  • Reflect
  • Promise 对象
  • Iterator 和 for-of 循环
  • Generator Function Syntax
  • Asynchronous Applications of Generator Functions
  • async Functions
    • Meaning
    • Basic Usage
    • Syntax
      • Returning Promise Objects
      • State Changes of the Promise Object
      • The await Command
      • Error Handling
      • Usage Notes
    • Implementation Principles of async Functions
    • Comparison with Other Asynchronous Processing Methods
    • Example: Completing Asynchronous Operations in Order
    • Top-Level await
  • Class 的基本语法
  • Class 的继承
  • Module 的语法
  • Module 的加载实现
  • 编程风格
  • 读懂 ECMAScript 规格
  • Async Iterator
  • ArrayBuffer
  • 最新提案
  • 装饰器
  • 函数式编程
  • Mixin
  • SIMD
  • 参考链接
  • 《ES6 教程》笔记
阮一峰
2020-02-09
Contents

async Functions

# async Functions

# Meaning

The ES2017 standard introduced async functions, making asynchronous operations even more convenient.

What are async functions? In one sentence, they are syntactic sugar for Generator functions.

A previous section had a Generator function that reads two files in sequence.

const fs = require('fs');

const readFile = function (fileName) {
  return new Promise(function (resolve, reject) {
    fs.readFile(fileName, function(error, data) {
      if (error) return reject(error);
      resolve(data);
    });
  });
};

const gen = function* () {
  const f1 = yield readFile('/etc/fstab');
  const f2 = yield readFile('/etc/shells');
  console.log(f1.toString());
  console.log(f2.toString());
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

The function gen in the code above can be written as an async function like this.

const asyncReadFile = async function () {
  const f1 = await readFile('/etc/fstab');
  const f2 = await readFile('/etc/shells');
  console.log(f1.toString());
  console.log(f2.toString());
};
1
2
3
4
5
6

A comparison shows that the async function simply replaces the asterisk (*) of the Generator function with async, and replaces yield with await — that is all.

The improvements that async functions make over Generator functions are reflected in the following four points.

(1) Built-in executor.

Generator function execution depends on an executor, which is why the co module exists. But async functions have their own built-in executor. In other words, the execution of an async function is exactly like a regular function — just one line.

asyncReadFile();
1

The code above calls the asyncReadFile function, and it automatically executes and outputs the final result. This is completely unlike Generator functions, which require calling the next method, or using the co module, to actually execute and get the final result.

(2) Better semantics.

async and await have clearer semantics than the asterisk and yield. async indicates that the function contains asynchronous operations, and await indicates that the expression immediately following it needs to wait for a result.

(3) Wider applicability.

The co module specifies that only Thunk functions or Promise objects can follow the yield command, while await in async functions can be followed by Promise objects and primitive values (numbers, strings, and booleans — though these are automatically converted to immediately resolved Promise objects).

(4) The return value is a Promise.

The return value of an async function is a Promise object, which is much more convenient than the Iterator object returned by Generator functions. You can use the then method to specify the next operation.

Further, an async function can be viewed as multiple asynchronous operations wrapped into a single Promise object, and the await command is syntactic sugar for the internal then command.

# Basic Usage

An async function returns a Promise object, and you can use the then method to add callback functions. When the function executes, upon encountering await it returns first, waits for the asynchronous operation to complete, and then continues executing the rest of the function body.

Below is an example.

async function getStockPriceByName(name) {
  const symbol = await getStockSymbol(name);
  const stockPrice = await getStockPrice(symbol);
  return stockPrice;
}

getStockPriceByName('goog').then(function (result) {
  console.log(result);
});
1
2
3
4
5
6
7
8
9

The code above is a function that gets a stock price. The async keyword before the function indicates that there are asynchronous operations inside the function. When the function is called, it immediately returns a Promise object.

Below is another example that outputs a value after a specified number of milliseconds.

function timeout(ms) {
  return new Promise((resolve) => {
    setTimeout(resolve, ms);
  });
}

async function asyncPrint(value, ms) {
  await timeout(ms);
  console.log(value);
}

asyncPrint('hello world', 50);
1
2
3
4
5
6
7
8
9
10
11
12

The code above specifies that after 50 milliseconds, hello world is output.

Since async functions return Promise objects, they can be used as parameters to the await command. So the example above can also be written as follows.

async function timeout(ms) {
  await new Promise((resolve) => {
    setTimeout(resolve, ms);
  });
}

async function asyncPrint(value, ms) {
  await timeout(ms);
  console.log(value);
}

asyncPrint('hello world', 50);
1
2
3
4
5
6
7
8
9
10
11
12

async functions have several forms of usage.

// Function declaration
async function foo() {}

// Function expression
const foo = async function () {};

// Object method
let obj = { async foo() {} };
obj.foo().then(...)

// Class method
class Storage {
  constructor() {
    this.cachePromise = caches.open('avatars');
  }

  async getAvatar(name) {
    const cache = await this.cachePromise;
    return cache.match(`/avatars/${name}.jpg`);
  }
}

const storage = new Storage();
storage.getAvatar('jake').then(…);

// Arrow function
const foo = async () => {};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

# Syntax

The syntax rules of async functions are relatively simple overall. The difficult part is the error handling mechanism.

# Returning Promise Objects

An async function returns a Promise object.

The value returned by the return statement inside an async function becomes the parameter of the then method's callback function.

async function f() {
  return 'hello world';
}

f().then(v => console.log(v))
// "hello world"
1
2
3
4
5
6

In the code above, the value returned by the return command inside function f is received by the then method's callback function.

Throwing an error inside an async function causes the returned Promise object to become a reject state. The thrown error object is received by the catch method's callback function.

async function f() {
  throw new Error('出错了');
}

f().then(
  v => console.log(v),
  e => console.log(e)
)
// Error: 出错了
1
2
3
4
5
6
7
8
9

# State Changes of the Promise Object

The Promise object returned by an async function must wait until all Promise objects after internal await commands have finished executing before a state change occurs, unless a return statement is encountered or an error is thrown. In other words, only after all internal asynchronous operations of the async function have completed will the callback function specified by the then method be executed.

Below is an example.

async function getTitle(url) {
  let response = await fetch(url);
  let html = await response.text();
  return html.match(/<title>([\s\S]+)<\/title>/i)[1];
}
getTitle('https://tc39.github.io/ecma262/').then(console.log)
// "ECMAScript 2017 Language Specification"
1
2
3
4
5
6
7

In the code above, function getTitle has three internal operations: fetching a webpage, extracting text, and matching the page title. Only after all three operations are complete is the console.log inside the then method executed.

# The await Command

Under normal circumstances, the await command is followed by a Promise object and returns the result of that object. If it is not a Promise object, the corresponding value is returned directly.

async function f() {
  // Equivalent to
  // return 123;
  return await 123;
}

f().then(v => console.log(v))
// 123
1
2
3
4
5
6
7
8

In the code above, the parameter of the await command is the numeric value 123, which is equivalent to return 123.

Another case is when the await command is followed by a thenable object (i.e., an object that defines a then method), in which case await treats it as equivalent to a Promise object.

class Sleep {
  constructor(timeout) {
    this.timeout = timeout;
  }
  then(resolve, reject) {
    const startTime = Date.now();
    setTimeout(
      () => resolve(Date.now() - startTime),
      this.timeout
    );
  }
}

(async () => {
  const sleepTime = await new Sleep(1000);
  console.log(sleepTime);
})();
// 1000
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

In the code above, the await command is followed by an instance of the Sleep object. This instance is not a Promise object, but because it defines a then method, await treats it as a Promise.

This example also demonstrates how to implement a sleep effect. JavaScript has never had sleep syntax, but with the await command, a program can be paused for a specified time. Below is a simplified sleep implementation.

function sleep(interval) {
  return new Promise(resolve => {
    setTimeout(resolve, interval);
  })
}

// Usage
async function one2FiveInAsync() {
  for(let i = 1; i <= 5; i++) {
    console.log(i);
    await sleep(1000);
  }
}

one2FiveInAsync();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

If the Promise object after the await command changes to a reject state, the reject parameter is received by the catch method's callback function.

async function f() {
  await Promise.reject('出错了');
}

f()
.then(v => console.log(v))
.catch(e => console.log(e))
// 出错了
1
2
3
4
5
6
7
8

Note that in the code above, there is no return before the await statement, but the reject method's parameter is still passed to the catch method's callback function. Adding return before await would produce the same effect.

If any await statement's following Promise object changes to reject state, the entire async function will stop executing.

async function f() {
  await Promise.reject('出错了');
  await Promise.resolve('hello world'); // Will not execute
}
1
2
3
4

In the code above, the second await statement will not execute because the first await statement's state changed to reject.

Sometimes we want the subsequent asynchronous operation not to be interrupted even if a previous one fails. In this case, the first await can be placed inside a try...catch block so that the second await will execute regardless of whether the first asynchronous operation succeeds.

async function f() {
  try {
    await Promise.reject('出错了');
  } catch(e) {
  }
  return await Promise.resolve('hello world');
}

f()
.then(v => console.log(v))
// hello world
1
2
3
4
5
6
7
8
9
10
11

Another approach is to chain a catch method after the Promise object following await to handle potential errors from before.

async function f() {
  await Promise.reject('出错了')
    .catch(e => console.log(e));
  return await Promise.resolve('hello world');
}

f()
.then(v => console.log(v))
// 出错了
// hello world
1
2
3
4
5
6
7
8
9
10

# Error Handling

If the asynchronous operation after await errors, it is equivalent to the Promise object returned by the async function being rejected.

async function f() {
  await new Promise(function (resolve, reject) {
    throw new Error('出错了');
  });
}

f()
.then(v => console.log(v))
.catch(e => console.log(e))
// Error:出错了
1
2
3
4
5
6
7
8
9
10

In the code above, after async function f executes, the Promise object after await throws an error object, causing the catch method's callback to be called with the thrown error object as its parameter. For the specific execution mechanism, see "Implementation Principles of async Functions" below.

The way to prevent errors is to place it inside a try...catch block.

async function f() {
  try {
    await new Promise(function (resolve, reject) {
      throw new Error('出错了');
    });
  } catch(e) {
  }
  return await('hello world');
}
1
2
3
4
5
6
7
8
9

If there are multiple await commands, they can be placed together in a try...catch block.

async function main() {
  try {
    const val1 = await firstStep();
    const val2 = await secondStep(val1);
    const val3 = await thirdStep(val1, val2);

    console.log('Final: ', val3);
  }
  catch (err) {
    console.error(err);
  }
}
1
2
3
4
5
6
7
8
9
10
11
12

The example below uses a try...catch block to implement multiple retry attempts.

const superagent = require('superagent');
const NUM_RETRIES = 3;

async function test() {
  let i;
  for (i = 0; i < NUM_RETRIES; ++i) {
    try {
      await superagent.get('http://google.com/this-throws-an-error');
      break;
    } catch(err) {}
  }
  console.log(i); // 3
}

test();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

In the code above, if the await operation succeeds, the break statement exits the loop; if it fails, it is caught by the catch statement and enters the next loop iteration.

# Usage Notes

Point one: as mentioned earlier, the Promise object after the await command may have a rejected result, so it is best to place await commands inside try...catch blocks.

async function myFunction() {
  try {
    await somethingThatReturnsAPromise();
  } catch (err) {
    console.log(err);
  }
}

// Alternative notation

async function myFunction() {
  await somethingThatReturnsAPromise()
  .catch(function (err) {
    console.log(err);
  });
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

Point two: if the asynchronous operations after multiple await commands have no sequential dependency, it is best to trigger them simultaneously (concurrently).

let foo = await getFoo();
let bar = await getBar();
1
2

In the code above, getFoo and getBar are two independent asynchronous operations (i.e., they do not depend on each other) but are written with a sequential relationship. This is time-consuming because getBar only executes after getFoo completes; they could easily be triggered simultaneously.

// Approach one
let [foo, bar] = await Promise.all([getFoo(), getBar()]);

// Approach two
let fooPromise = getFoo();
let barPromise = getBar();
let foo = await fooPromise;
let bar = await barPromise;
1
2
3
4
5
6
7
8

In both approaches above, getFoo and getBar are triggered simultaneously, which shortens the program's execution time.

Point three: the await command can only be used inside async functions; using it in regular functions will cause an error.

async function dbFuc(db) {
  let docs = [{}, {}, {}];

  // Error
  docs.forEach(function (doc) {
    await db.post(doc);
  });
}
1
2
3
4
5
6
7
8

The code above will error because await is used inside a regular function. However, if the parameter of forEach is changed to an async function, there are still problems.

function dbFuc(db) { // async is not needed here
  let docs = [{}, {}, {}];

  // May get wrong results
  docs.forEach(async function (doc) {
    await db.post(doc);
  });
}
1
2
3
4
5
6
7
8

The code above may not work correctly because the three db.post operations will execute concurrently (i.e., simultaneously), not sequentially. The correct way is to use a for loop.

async function dbFuc(db) {
  let docs = [{}, {}, {}];

  for (let doc of docs) {
    await db.post(doc);
  }
}
1
2
3
4
5
6
7

If you indeed want multiple requests to execute concurrently, you can use the Promise.all method. When all three requests are resolved, the two notations below produce the same effect.

async function dbFuc(db) {
  let docs = [{}, {}, {}];
  let promises = docs.map((doc) => db.post(doc));

  let results = await Promise.all(promises);
  console.log(results);
}

// Or use the following notation

async function dbFuc(db) {
  let docs = [{}, {}, {}];
  let promises = docs.map((doc) => db.post(doc));

  let results = [];
  for (let promise of promises) {
    results.push(await promise);
  }
  console.log(results);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

Point four: async functions can preserve the runtime stack.

const a = () => {
  b().then(() => c());
};
1
2
3

In the code above, function a runs an asynchronous task b() internally. While b() is running, function a() does not stop but continues executing. By the time b() finishes, a() may have long since finished, and b()'s context has disappeared. If b() or c() errors, the error stack will not include a().

Now let us change this example to use an async function.

const a = async () => {
  await b();
  c();
};
1
2
3
4

In the code above, while b() is running, a() is paused and its context is preserved. If b() or c() errors, the error stack will include a().

# Implementation Principles of async Functions

The implementation principle of async functions is wrapping Generator functions and an automatic executor together in one function.

async function fn(args) {
  // ...
}

// Equivalent to

function fn(args) {
  return spawn(function* () {
    // ...
  });
}
1
2
3
4
5
6
7
8
9
10
11

All async functions can be written in the second form above, where the spawn function is the automatic executor.

Below is the implementation of the spawn function, which is basically a copy of the automatic executor from the earlier section.

function spawn(genF) {
  return new Promise(function(resolve, reject) {
    const gen = genF();
    function step(nextF) {
      let next;
      try {
        next = nextF();
      } catch(e) {
        return reject(e);
      }
      if(next.done) {
        return resolve(next.value);
      }
      Promise.resolve(next.value).then(function(v) {
        step(function() { return gen.next(v); });
      }, function(e) {
        step(function() { return gen.throw(e); });
      });
    }
    step(function() { return gen.next(undefined); });
  });
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# Comparison with Other Asynchronous Processing Methods

Let us use an example to compare async functions with Promise and Generator functions.

Suppose a series of animations are deployed on a certain DOM element, where each animation must finish before the next one begins. If any animation in the sequence errors, stop continuing and return the return value of the last successfully executed animation.

First, the Promise approach.

function chainAnimationsPromise(elem, animations) {

  // Variable ret saves the return value of the previous animation
  let ret = null;

  // Create a new empty Promise
  let p = Promise.resolve();

  // Use the then method to add all animations
  for(let anim of animations) {
    p = p.then(function(val) {
      ret = val;
      return anim(elem);
    });
  }

  // Return a Promise with error-catching mechanism deployed
  return p.catch(function(e) {
    /* Ignore error, continue execution */
  }).then(function() {
    return ret;
  });

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

Although the Promise approach is a major improvement over the callback function approach, at first glance the code is entirely Promise API (then, catch, etc.), and the semantics of the operations themselves are not easy to discern.

Next, the Generator function approach.

function chainAnimationsGenerator(elem, animations) {

  return spawn(function*() {
    let ret = null;
    try {
      for(let anim of animations) {
        ret = yield anim(elem);
      }
    } catch(e) {
      /* Ignore error, continue execution */
    }
    return ret;
  });

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

The code above uses Generator functions to iterate through each animation. The semantics are clearer than Promise, and all user-defined operations appear inside the spawn function. The problem with this approach is that a task runner is required to automatically execute the Generator function. The spawn function in the code above is the automatic executor, which returns a Promise object and must ensure that expressions after yield return Promises.

Finally, the async function approach.

async function chainAnimationsAsync(elem, animations) {
  let ret = null;
  try {
    for(let anim of animations) {
      ret = await anim(elem);
    }
  } catch(e) {
    /* Ignore error, continue execution */
  }
  return ret;
}
1
2
3
4
5
6
7
8
9
10
11

The async function implementation is the most concise and most semantically clear, with almost no semantically irrelevant code. It moves the automatic executor from the Generator approach to the language level, not exposing it to users, thus requiring the least amount of code. If using the Generator approach, the automatic executor must be provided by the user.

# Example: Completing Asynchronous Operations in Order

In practical development, one often encounters a group of asynchronous operations that need to be completed in order. For example, reading a group of URLs remotely in sequence and then outputting the results in the order they were read.

The Promise approach is as follows.

function logInOrder(urls) {
  // Read all URLs remotely
  const textPromises = urls.map(url => {
    return fetch(url).then(response => response.text());
  });

  // Output in order
  textPromises.reduce((chain, textPromise) => {
    return chain.then(() => textPromise)
      .then(text => console.log(text));
  }, Promise.resolve());
}
1
2
3
4
5
6
7
8
9
10
11
12

The code above uses the fetch method to read a group of URLs concurrently. Each fetch operation returns a Promise object placed into the textPromises array. Then, the reduce method processes each Promise object in order, chaining all Promise objects together using then so the results can be output sequentially.

This approach is not very intuitive and has poor readability. Below is the async function implementation.

async function logInOrder(urls) {
  for (const url of urls) {
    const response = await fetch(url);
    console.log(await response.text());
  }
}
1
2
3
4
5
6

The code above is indeed greatly simplified, but the problem is that all remote operations are sequential. Only after the previous URL returns a result does it read the next URL, which is very inefficient and wastes time. What we need is to issue remote requests concurrently.

async function logInOrder(urls) {
  // Read remote URLs concurrently
  const textPromises = urls.map(async url => {
    const response = await fetch(url);
    return response.text();
  });

  // Output in order
  for (const textPromise of textPromises) {
    console.log(await textPromise);
  }
}
1
2
3
4
5
6
7
8
9
10
11
12

In the code above, although the map method's parameter is an async function, it executes concurrently because only the inside of the async function executes sequentially; the outside is not affected. The subsequent for..of loop uses await internally, thus achieving ordered output.

# Top-Level await

According to the syntax specification, the await command can only appear inside async functions; otherwise, an error is thrown.

// Error
const data = await fetch('https://api.example.com');
1
2

In the code above, the await command is used independently without being placed inside an async function, which causes an error.

Currently, there is a syntax proposal (opens new window) that allows using the await command independently at the top level of a module. The purpose of this proposal is to use await to solve the problem of asynchronous module loading.

// awaiting.js
let output;
async function main() {
  const dynamic = await import(someMission);
  const data = await fetch(url);
  output = someProcess(dynamic.default, data);
}
main();
export { output };
1
2
3
4
5
6
7
8
9

In the code above, the output value output of the awaiting.js module depends on asynchronous operations. We wrap the asynchronous operations in an async function and call this function. Only after the asynchronous operations inside it all execute will the variable output have a value; otherwise, it returns undefined.

The code above can also be written as an immediately-invoked function expression.

// awaiting.js
let output;
(async function main() {
  const dynamic = await import(someMission);
  const data = await fetch(url);
  output = someProcess(dynamic.default, data);
})();
export { output };
1
2
3
4
5
6
7
8

Below is how to load this module.

// usage.js
import { output } from "./awaiting.js";

function outputPlusValue(value) { return output + value }

console.log(outputPlusValue(100));
setTimeout(() => console.log(outputPlusValue(100), 1000);
1
2
3
4
5
6
7

In the code above, the result of outputPlusValue() depends entirely on when it is executed. If the asynchronous operations in awaiting.js have not completed, the imported output value will be undefined.

The current solution is to have the original module export a Promise object and use this Promise object to determine whether the asynchronous operations have ended.

// awaiting.js
let output;
export default (async function main() {
  const dynamic = await import(someMission);
  const data = await fetch(url);
  output = someProcess(dynamic.default, data);
})();
export { output };
1
2
3
4
5
6
7
8

In the code above, awaiting.js exports not only output but also a default Promise object (the immediately-invoked async function returns a Promise object), which is used to determine whether the asynchronous operations have ended.

Below is the new way to load this module.

// usage.js
import promise, { output } from "./awaiting.js";

function outputPlusValue(value) { return output + value }

promise.then(() => {
  console.log(outputPlusValue(100));
  setTimeout(() => console.log(outputPlusValue(100), 1000);
});
1
2
3
4
5
6
7
8
9

In the code above, the output of the awaiting.js object is placed inside promise.then(), ensuring that output is read only after the asynchronous operations are complete.

This approach is rather cumbersome, essentially requiring module users to follow an additional usage protocol and use the module in a special way. If you forget to load with Promise and only use the normal loading method, code dependent on this module may error. Moreover, if usage.js above also has external output, it means all modules in the dependency chain must use Promise loading.

The top-level await command is designed to solve this problem. It ensures that the module only outputs values after asynchronous operations are complete.

// awaiting.js
const dynamic = import(someMission);
const data = fetch(url);
export const output = someProcess((await dynamic).default, await data);
1
2
3
4

In the code above, both asynchronous operations have the await command added when outputting. Only after the asynchronous operations complete will this module output values.

Loading this module is written as follows.

// usage.js
import { output } from "./awaiting.js";
function outputPlusValue(value) { return output + value }

console.log(outputPlusValue(100));
setTimeout(() => console.log(outputPlusValue(100), 1000);
1
2
3
4
5
6

The code above is written exactly like normal module loading. In other words, module users do not need to care whether the dependent module has internal asynchronous operations; they just load normally.

In this case, module loading waits for the dependent module's (in this case awaiting.js) asynchronous operations to complete before executing subsequent code, somewhat like pausing there. So it always gets the correct output and will not get different values due to different loading timing.

Below are some usage scenarios for top-level await.

// import() method loading
const strings = await import(`/i18n/${navigator.language}`);

// Database operations
const connection = await dbConnector();

// Dependency fallback
let jQuery;
try {
  jQuery = await import('https://cdn-a.com/jQuery');
} catch {
  jQuery = await import('https://cdn-b.com/jQuery');
}
1
2
3
4
5
6
7
8
9
10
11
12
13

Note that if loading multiple modules that contain top-level await commands, the load commands are executed synchronously.

// x.js
console.log("X1");
await new Promise(r => setTimeout(r, 1000));
console.log("X2");

// y.js
console.log("Y");

// z.js
import "./x.js";
import "./y.js";
console.log("Z");
1
2
3
4
5
6
7
8
9
10
11
12

The code above has three modules. The last one, z.js, loads x.js and y.js. The print result is X1, Y, X2, Z. This shows that z.js did not wait for x.js to finish loading before loading y.js.

The top-level await command is somewhat like handing over code execution control to other module loads, and after the asynchronous operations complete, taking back execution control and continuing execution.

Edit (opens new window)
#ES6
Last Updated: 2026/03/21, 12:14:36
Asynchronous Applications of Generator Functions
Class 的基本语法

← Asynchronous Applications of Generator Functions Class 的基本语法→

Recent Updates
01
How I Discovered Disposable Email — A True Story
06-12
02
Animations in Grid Layout
09-15
03
Renaming a Git Branch
08-11
More Articles >
Theme by VDone | Copyright © 2026-2026 Nikolay Tuzov | MIT License | Telegram
  • Auto
  • Light Mode
  • Dark Mode
  • Reading Mode