阿里云主机折上折
  • 微信号
Current Site:Index > async/await asynchronous programming

async/await asynchronous programming

Author:Chuan Chen 阅读数:14207人阅读 分类: JavaScript

ECMAScript 8 async/await Asynchronous Programming

ECMAScript 8 (ES2017) introduced the async/await syntax, making Promise-based asynchronous code look more like synchronous code. This syntactic sugar revolutionized JavaScript asynchronous programming, making code more readable and maintainable.

Basics of async Functions

An async function is declared using the async keyword and always returns a Promise. If a non-Promise value is returned inside an async function, JavaScript automatically wraps it in a resolved Promise.

async function basicAsync() {
  return 'Hello Async';
}

basicAsync().then(console.log); // Output: "Hello Async"

Inside an async function, the await keyword can be used to pause execution until the awaited Promise is resolved or rejected. await can only be used inside async functions.

async function fetchData() {
  const response = await fetch('https://api.example.com/data');
  const data = await response.json();
  return data;
}

Detailed Explanation of await Expressions

The await expression pauses the execution of the async function, waits for the Promise to resolve, then resumes the async function's execution and returns the resolved value. If the awaited value is not a Promise, it is converted into a resolved Promise.

async function awaitDemo() {
  const result = await new Promise(resolve => {
    setTimeout(() => resolve('Done!'), 1000);
  });
  console.log(result); // Output after 1 second: "Done!"
}

When a Promise is rejected, await throws the rejection reason, which can be caught using try/catch:

async function handleError() {
  try {
    await Promise.reject('Something went wrong');
  } catch (error) {
    console.error('Caught error:', error);
  }
}

Parallel Execution of Multiple Asynchronous Operations

Although await executes sequentially, we can achieve parallelism with some techniques:

async function parallelTasks() {
  const [user, posts] = await Promise.all([
    fetch('/user'),
    fetch('/posts')
  ]);
  
  // Both requests proceed simultaneously, execution continues only after both complete
}

Another approach is to start all Promises first, then await them:

async function parallelAwait() {
  const userPromise = fetch('/user');
  const postsPromise = fetch('/posts');
  
  const user = await userPromise;
  const posts = await postsPromise;
}

Error Handling Patterns

async/await offers multiple error handling approaches:

  1. Traditional try/catch:
async function tryCatchExample() {
  try {
    const data = await fetchData();
  } catch (error) {
    console.error('Fetch failed:', error);
  }
}
  1. Adding catch at the end of the Promise chain:
async function promiseCatchExample() {
  const data = await fetchData().catch(error => {
    console.error('Fetch failed:', error);
  });
}
  1. Using a helper function wrapper:
function to(promise) {
  return promise.then(data => [null, data]).catch(err => [err]);
}

async function usingHelper() {
  const [err, data] = await to(fetchData());
  if (err) console.error('Error:', err);
}

Interoperability Between async Functions and Promises

Since async functions return Promises, they can seamlessly integrate with existing Promise code:

async function getUser(id) {
  return { id, name: 'John Doe' };
}

// Using async functions in Promise chains
getUser(1)
  .then(user => console.log(user))
  .catch(console.error);

// Using Promises in async functions
async function processUser() {
  const user = await getUser(1);
  console.log(user);
}

Common Pitfalls and Best Practices

  1. Avoid unnecessary await:
// Bad - unnecessary await
async function slowFunc() {
  const result = await doSomething();
  return await processResult(result);
}

// Better
async function fastFunc() {
  const result = await doSomething();
  return processResult(result); // processResult returns a Promise
}
  1. await in loops:
// Sequential execution - each await waits for the previous one to complete
async function processArray(array) {
  for (const item of array) {
    await processItem(item);
  }
}

// Parallel execution
async function processArrayParallel(array) {
  await Promise.all(array.map(item => processItem(item)));
}
  1. Top-level await: In ES modules, await can be used directly
// module.js
const data = await fetchData();
export default data;

Advanced Patterns

  1. Retry logic:
async function retry(fn, retries = 3, delay = 1000) {
  try {
    return await fn();
  } catch (error) {
    if (retries <= 0) throw error;
    await new Promise(resolve => setTimeout(resolve, delay));
    return retry(fn, retries - 1, delay * 2);
  }
}
  1. Timeout control:
async function withTimeout(promise, timeout) {
  let timer;
  const timeoutPromise = new Promise((_, reject) => {
    timer = setTimeout(() => reject(new Error('Timeout')), timeout);
  });
  
  try {
    return await Promise.race([promise, timeoutPromise]);
  } finally {
    clearTimeout(timer);
  }
}
  1. Sequential processing of dynamically generated Promises:
async function processInOrder(generator) {
  for await (const promise of generator) {
    try {
      const result = await promise;
      console.log(result);
    } catch (error) {
      console.error(error);
    }
  }
}

async Functions as Class Methods

async functions can also be used as class methods:

class ApiClient {
  constructor(baseUrl) {
    this.baseUrl = baseUrl;
  }
  
  async get(endpoint) {
    const response = await fetch(`${this.baseUrl}${endpoint}`);
    return response.json();
  }
}

// Usage
const client = new ApiClient('https://api.example.com');
client.get('/users').then(console.log);

async Generator Functions

ES2018 introduced async generator functions, which can yield Promises:

async function* asyncGenerator() {
  let i = 0;
  while (i < 3) {
    await new Promise(resolve => setTimeout(resolve, 1000));
    yield i++;
  }
}

(async () => {
  for await (const num of asyncGenerator()) {
    console.log(num); // Outputs 0, 1, 2 with 1-second intervals
  }
})();

Performance Considerations

While async/await improves code clarity, be aware of:

  1. Each await pauses the function, creating additional Promise objects
  2. Unnecessary sequential await can degrade performance
  3. Error stacks may not be as clear as in synchronous code
// Poor performance example
async function poorPerformance() {
  const a = await taskA(); // Wait for A to complete
  const b = await taskB(); // Then wait for B to complete
  return a + b;
}

// Improved version
async function betterPerformance() {
  const [a, b] = await Promise.all([taskA(), taskB()]);
  return a + b;
}

Browser Compatibility and Transpilation

While modern browsers support async/await, older environments require transpilation tools like Babel:

// Original async/await before transpilation
async function fetchUser() {
  const response = await fetch('/user');
  return response.json();
}

// Transpiled code roughly looks like this
function fetchUser() {
  return Promise.resolve().then(function() {
    return fetch('/user');
  }).then(function(response) {
    return response.json();
  });
}

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.