阿里云主机折上折
  • 微信号
Current Site:Index > The await expression translates this sentence into English.

The await expression translates this sentence into English.

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

Basic Concepts of the await Expression

The await expression, introduced in ECMAScript 6, is a core component of the async/await asynchronous programming model. It can only be used inside async functions and is used to pause the execution of an async function, wait for a Promise to resolve, and then resume the async function's execution and return the resolved value.

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

The await expression suspends the execution of the current async function until the awaited Promise's state becomes fulfilled or rejected. If the Promise is fulfilled, the await expression returns the resolved value; if the Promise is rejected, the await expression throws the rejection reason.

Syntax of the await Expression

The basic syntax of the await expression is very simple:

[rv] = await expression;

Where:

  • expression: A Promise object or any value to wait for
  • rv (optional): The returned Promise resolution value

When the value following await is not a Promise, the engine converts it to a resolved Promise using Promise.resolve():

async function foo() {
  const a = await 42; // Equivalent to await Promise.resolve(42)
  console.log(a); // 42
}

Comparison Between await and Promise Chaining

Before ES6, handling asynchronous operations primarily relied on Promise chaining:

function fetchData() {
  return fetch('https://api.example.com/data')
    .then(response => response.json())
    .then(data => {
      console.log(data);
      return data;
    })
    .catch(error => {
      console.error('Error:', error);
    });
}

Using await allows expressing the same logic more intuitively:

async function fetchData() {
  try {
    const response = await fetch('https://api.example.com/data');
    const data = await response.json();
    console.log(data);
    return data;
  } catch (error) {
    console.error('Error:', error);
  }
}

Error Handling with await

There are several ways to handle errors that may be thrown by await expressions:

  1. Using try/catch blocks:
async function getUser(id) {
  try {
    const response = await fetch(`/api/users/${id}`);
    return await response.json();
  } catch (error) {
    console.error('Failed to fetch user:', error);
    throw error; // Optionally rethrow the error
  }
}
  1. Handling errors when calling the async function:
async function main() {
  const user = await getUser(123).catch(error => {
    console.error('Error in main:', error);
    return null;
  });
  
  if (user) {
    console.log('User:', user);
  }
}
  1. Combining with Promise's catch method:
async function loadData() {
  const data = await fetchData().catch(handleError);
  // Process data...
}

Using await in Loops

await can be used in various loop structures to implement sequentially executed asynchronous operations:

  1. for loop:
async function processArray(array) {
  for (let i = 0; i < array.length; i++) {
    const result = await processItem(array[i]);
    console.log(result);
  }
}
  1. for...of loop:
async function processItems(items) {
  for (const item of items) {
    await processItem(item);
  }
}

Note: Using await in array methods like forEach or map will not work as expected, as these methods do not wait for async callbacks to complete:

// Incorrect example - won't wait
array.forEach(async item => {
  await processItem(item);
});

// Correct approach
await Promise.all(array.map(async item => {
  await processItem(item);
}));

Parallel Execution with await

Although await pauses the execution of the current async function, parallel execution can be achieved through appropriate methods:

  1. Using Promise.all:
async function fetchMultipleUrls(urls) {
  const promises = urls.map(url => fetch(url));
  const responses = await Promise.all(promises);
  return await Promise.all(responses.map(r => r.json()));
}
  1. Starting Promises early:
async function parallelTasks() {
  const promise1 = doTask1(); // Starts execution immediately
  const promise2 = doTask2(); // Starts execution immediately
  
  const result1 = await promise1;
  const result2 = await promise2;
  
  return { result1, result2 };
}

await in Top-Level Scope

Before ES2022, await could not be used directly in the top-level scope of a module and had to be wrapped in an async function:

// Before ES2022
(async () => {
  const data = await fetchData();
  console.log(data);
})();

// ES2022 and later (top-level await)
const data = await fetchData();
console.log(data);

Performance Considerations with await

While await makes asynchronous code more readable, improper use can impact performance:

  1. Avoid unnecessary sequential waiting:
// Inefficient
async function slowExample() {
  const a = await getA();
  const b = await getB(); // Waits for getA to complete before starting
  
  return a + b;
}

// Efficient
async function fastExample() {
  const [a, b] = await Promise.all([getA(), getB()]);
  return a + b;
}
  1. Batch processing asynchronous operations:
async function processInBatches(items, batchSize) {
  for (let i = 0; i < items.length; i += batchSize) {
    const batch = items.slice(i, i + batchSize);
    await Promise.all(batch.map(processItem));
  }
}

Advanced Usage of await

  1. Combining with destructuring assignment:
async function getUserAndPosts(userId) {
  const [user, posts] = await Promise.all([
    fetchUser(userId),
    fetchPosts(userId)
  ]);
  
  return { user, posts };
}
  1. Implementing timeout control:
async function fetchWithTimeout(url, timeout) {
  const controller = new AbortController();
  const timeoutId = setTimeout(() => controller.abort(), timeout);
  
  try {
    const response = await fetch(url, { signal: controller.signal });
    clearTimeout(timeoutId);
    return response;
  } catch (error) {
    clearTimeout(timeoutId);
    throw error;
  }
}
  1. Conditional waiting:
async function processIfReady(id) {
  const data = await getData(id);
  
  if (data.needsMoreProcessing) {
    await processFurther(data);
  }
  
  return data;
}

await and Generator Functions

async/await is essentially syntactic sugar combining generator functions and Promises. Understanding this helps master await's working principles:

// Using generator functions to simulate async/await
function* generatorExample() {
  const result = yield somePromise;
  console.log(result);
}

// Running the generator
function runGenerator(generator) {
  const iterator = generator();
  
  function handle(result) {
    if (result.done) return;
    result.value.then(
      res => handle(iterator.next(res)),
      err => iterator.throw(err)
    );
  }
  
  handle(iterator.next());
}

runGenerator(generatorExample);

Using await in Class Methods

await can be used in class async methods:

class ApiClient {
  constructor(baseUrl) {
    this.baseUrl = baseUrl;
  }
  
  async get(resource) {
    const response = await fetch(`${this.baseUrl}/${resource}`);
    return response.json();
  }
  
  static async create(config) {
    const client = new ApiClient(config.baseUrl);
    await client.initialize();
    return client;
  }
}

await and Module Systems

In ES modules, top-level await can be used to execute module initialization logic:

// config.js
const config = await fetch('/config.json').then(r => r.json());
export { config };

// app.js
import { config } from './config.js';

console.log('App config:', config);

Browser Compatibility of await

Modern browsers generally support async/await, but transpilation may be needed for older environments:

  1. Using Babel transpilation:
{
  "presets": ["@babel/preset-env"]
}
  1. In unsupported environments, Promise and generator function polyfills can be used

Debugging Tips for await

When debugging async functions, note:

  1. In developer tools, async function call stacks will show the complete asynchronous call chain
  2. Breakpoints can be set before await expressions
  3. When using console.log for debugging, pay attention to execution order:
async function debugExample() {
  console.log('Start');
  const result = await someAsyncOp();
  console.log('After await'); // This executes after the async operation completes
  return result;
}

await and the Event Loop

Understanding how await interacts with the JavaScript event loop is important:

console.log('Script start');

async function asyncFunc() {
  console.log('Async function start');
  await Promise.resolve();
  console.log('Async function after await');
}

asyncFunc();

Promise.resolve().then(() => {
  console.log('Promise microtask');
});

console.log('Script end');

// Output order:
// Script start
// Async function start
// Script end
// Async function after await
// Promise microtask

await and Web APIs

await works well with various Web APIs:

  1. With the Fetch API:
async function postData(url, data) {
  const response = await fetch(url, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(data)
  });
  
  if (!response.ok) {
    throw new Error(`HTTP error! status: ${response.status}`);
  }
  
  return response.json();
}
  1. With Web Workers:
async function useWorker() {
  const worker = new Worker('worker.js');
  
  const result = await new Promise((resolve) => {
    worker.onmessage = (e) => resolve(e.data);
    worker.postMessage('start');
  });
  
  worker.terminate();
  return result;
}

Using await in Node.js

await is equally applicable in Node.js environments:

  1. With file system operations:
const fs = require('fs').promises;

async function readFiles() {
  try {
    const data1 = await fs.readFile('file1.txt', 'utf8');
    const data2 = await fs.readFile('file2.txt', 'utf8');
    return { data1, data2 };
  } catch (error) {
    console.error('Error reading files:', error);
  }
}
  1. With database operations:
async function getUserFromDB(userId) {
  const client = await pool.connect();
  
  try {
    const res = await client.query('SELECT * FROM users WHERE id = $1', [userId]);
    return res.rows[0];
  } finally {
    client.release();
  }
}

Common Pitfalls with await

When using await, be aware of some common issues:

  1. Forgetting the await keyword:
async function example() {
  const promise = someAsyncFunction(); // Missing await
  console.log(promise); // Outputs Promise object instead of result
}
  1. Unnecessary await:
async function unnecessaryAwait() {
  return await someAsyncFunction(); // Can directly return the Promise
}

// More concise version
async function better() {
  return someAsyncFunction();
}
  1. await blocking parallel operations:
// Inefficient
async function slow() {
  const a = await taskA();
  const b = await taskB(); // Waits for taskA to complete before starting
  return a + b;
}

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

await and Error Boundaries

In large applications, setting proper error boundaries is important:

async function renderComponent() {
  try {
    const data = await fetchData();
    return <MyComponent data={data} />;
  } catch (error) {
    return <ErrorFallback error={error} />;
  }
}

await and Cancellation Operations

Handling cancellable asynchronous operations:

async function cancellableFetch(url, signal) {
  const response = await fetch(url, { signal });
  return response.json();
}

// Usage
const controller = new AbortController();
const { signal } = controller;

try {
  const data = await cancellableFetch(url, signal);
  console.log(data);
} catch (error) {
  if (error.name === 'AbortError') {
    console.log('Fetch aborted');
  } else {
    console.error('Fetch error:', error);
  }
}

// Cancel the request
controller.abort();

await and State Management

When using await in state management, note:

class Store {
  constructor() {
    this.data = null;
    this.loading = false;
    this.error = null;
  }
  
  async loadData() {
    this.loading = true;
    this.error = null;
    
    try {
      this.data = await api.fetchData();
    } catch (error) {
      this.error = error;
    } finally {
      this.loading = false;
    }
  }
}

await and Testing

When testing async functions, testing frameworks typically support await:

describe('async functions', () => {
  it('should resolve with correct value', async () => {
    const result = await asyncFunction();
    expect(result).toEqual(expectedValue);
  });
  
  it('should reject with error', async () => {
    await expect(asyncFunction()).rejects.toThrow(Error);
  });
});

await and TypeScript

When using await in TypeScript, the type system correctly infers:

async function getUser(id: string): Promise<User> {
  const response = await fetch(`/api/users/${id}`);
  return response.json() as Promise<User>;
}

interface User {
  id: string;
  name: string;
  email: string;
}

await and React Components

Using await in React function components:

function DataFetcher() {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);
  
  useEffect(() => {
    async function fetchData() {
      setLoading(true);
      try {
        const result = await fetchData();
        setData(result);
      } catch (err) {
        setError(err);
      } finally {
        setLoading(false);
      }
    }
    
    fetchData();
  }, []);
  
  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;
  return <div>{JSON.stringify(data)}</div>;
}

await and Custom Promises

await can be used with any thenable object (an object with a then method):

class CustomThenable {
  constructor(value) {
    this.value = value;
  }
  
  then(resolve, reject) {
    setTimeout(() => resolve(this.value), 1000);
  }
}

async function example() {
  const result = await new CustomThenable(42);
  console.log(result); // Outputs 42 after 1 second
}

await and Recursion

await can be used in recursive functions:

async function retryOperation(operation, maxRetries = 3, delay = 1000) {
  try {
    return await operation();
  } catch (error) {
    if (maxRetries <= 0) throw error;
    await new Promise(resolve => setTimeout(resolve, delay));
    return retryOperation(operation, maxRetries - 1, delay * 2);
  }
}

await and Performance Optimization

Some techniques for optimizing await usage:

  1. Avoid unnecessary await in hot code paths
  2. Batch process asynchronous operations
  3. Use caching to reduce duplicate async calls
  4. Consider streaming for large datasets
async function processLargeDataset(dataset) {
  const batchSize = 100;
  const results = [];
  
  for (let i = 0; i < dataset.length; i += batchSize) {
    const batch = dataset.slice(i, i + batchSize);
    const batchResults = await Promise.all(batch.map(processItem));
    results.push(...batchResults);
  }
  
  return results;
}

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

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

上一篇:async函数语法

下一篇:错误处理模式

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 ☕.