阿里云主机折上折
  • 微信号
Current Site:Index > Common asynchronous pattern implementations

Common asynchronous pattern implementations

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

Basic Implementation of Promise

The Promise introduced in ECMAScript 6 is a core mechanism for handling asynchronous operations. A Promise object represents the eventual completion (or failure) of an asynchronous operation and its resulting value.

const promise = new Promise((resolve, reject) => {
  // Asynchronous operation
  setTimeout(() => {
    const success = Math.random() > 0.5;
    if (success) {
      resolve('Operation succeeded');
    } else {
      reject(new Error('Operation failed'));
    }
  }, 1000);
});

promise
  .then(result => console.log(result))
  .catch(error => console.error(error));

A Promise has three states:

  • pending: initial state
  • fulfilled: operation completed successfully
  • rejected: operation failed

Promise chaining allows multiple asynchronous operations to be linked together:

fetch('/api/user')
  .then(response => response.json())
  .then(user => fetch(`/api/posts/${user.id}`))
  .then(posts => console.log(posts))
  .catch(error => console.error('Request failed:', error));

async/await Syntactic Sugar

async/await is syntactic sugar built on top of Promises, making asynchronous code look like synchronous code.

async function fetchUserAndPosts() {
  try {
    const userResponse = await fetch('/api/user');
    const user = await userResponse.json();
    
    const postsResponse = await fetch(`/api/posts/${user.id}`);
    const posts = await postsResponse.json();
    
    console.log(posts);
  } catch (error) {
    console.error('Request failed:', error);
  }
}

An async function always returns a Promise:

async function getNumber() {
  return 42; // Equivalent to return Promise.resolve(42)
}

getNumber().then(value => console.log(value)); // 42

Generators and Coroutines

Generator functions can pause and resume execution, enabling coroutine-style asynchronous programming with yield.

function* asyncGenerator() {
  const result1 = yield new Promise(resolve => 
    setTimeout(() => resolve('Step 1 completed'), 1000)
  );
  console.log(result1);
  
  const result2 = yield new Promise(resolve => 
    setTimeout(() => resolve('Step 2 completed'), 1000)
  );
  console.log(result2);
}

function runGenerator(gen) {
  const iterator = gen();
  
  function iterate({ value, done }) {
    if (done) return;
    
    value.then(result => {
      iterate(iterator.next(result));
    }).catch(error => {
      iterator.throw(error);
    });
  }
  
  iterate(iterator.next());
}

runGenerator(asyncGenerator);

Promise Composition Methods

ES6 provides various Promise composition methods for handling multiple asynchronous operations.

Promise.all waits for all Promises to complete:

const [user, posts] = await Promise.all([
  fetch('/api/user').then(r => r.json()),
  fetch('/api/posts').then(r => r.json())
]);

Promise.race returns the first completed Promise:

const timeout = new Promise((_, reject) => 
  setTimeout(() => reject(new Error('Request timeout')), 5000);

const data = await Promise.race([
  fetch('/api/data'),
  timeout
]);

Promise.allSettled waits for all Promises to settle (regardless of success or failure):

const results = await Promise.allSettled([
  Promise.resolve('Success'),
  Promise.reject('Failure')
]);

/*
[
  { status: 'fulfilled', value: 'Success' },
  { status: 'rejected', reason: 'Failure' }
]
*/

Error Handling Patterns

Error handling in asynchronous code requires special attention.

Error catching in Promise chains:

fetch('/api/data')
  .then(response => {
    if (!response.ok) {
      throw new Error('Network response was not OK');
    }
    return response.json();
  })
  .catch(error => {
    console.error('Request failed:', error);
    return getFallbackData(); // Provide fallback data
  });

try/catch with async/await:

async function loadData() {
  try {
    const response = await fetch('/api/data');
    if (!response.ok) throw new Error('Network response was not OK');
    return await response.json();
  } catch (error) {
    console.error('Failed to load data:', error);
    return getFallbackData();
  }
}

Global unhandled Promise rejections:

window.addEventListener('unhandledrejection', event => {
  console.error('Unhandled Promise rejection:', event.reason);
  event.preventDefault();
});

Advanced Asynchronous Patterns

More complex asynchronous control patterns can be implemented using Promises.

Promise caching:

function createCachedFetch() {
  const cache = new Map();
  
  return async function cachedFetch(url) {
    if (cache.has(url)) {
      return cache.get(url);
    }
    
    const response = await fetch(url);
    const data = await response.json();
    cache.set(url, data);
    return data;
  };
}

Promise retry mechanism:

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);
  }
}

const data = await retry(() => fetch('/api/unstable'));

Promise timeout control:

function withTimeout(promise, timeout) {
  return Promise.race([
    promise,
    new Promise((_, reject) => 
      setTimeout(() => reject(new Error('Operation timeout')), timeout)
    )
  ]);
}

const result = await withTimeout(fetch('/api/slow'), 5000);

Asynchronous Iterators

Asynchronous iterators introduced in ES2018 handle asynchronous data streams.

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

(async function() {
  for await (const num of asyncGenerator()) {
    console.log(num); // 0, 1, 2 (output one per second)
  }
})();

Creating asynchronous iterators from Node.js streams:

import { createReadStream } from 'fs';

async function readFileLines(path) {
  const stream = createReadStream(path, { encoding: 'utf8' });
  
  for await (const chunk of stream) {
    const lines = chunk.split('\n');
    for (const line of lines) {
      console.log(line);
    }
  }
}

Asynchronous Operations in Web Workers

Using Promises in Web Workers for cross-thread communication.

Main thread code:

const worker = new Worker('worker.js');

function sendToWorker(data) {
  return new Promise((resolve) => {
    worker.onmessage = (e) => resolve(e.data);
    worker.postMessage(data);
  });
}

async function processData() {
  const result = await sendToWorker({ type: 'process', data: largeArray });
  console.log('Processing result:', result);
}

Worker thread code (worker.js):

self.onmessage = async (e) => {
  if (e.data.type === 'process') {
    const result = await heavyProcessing(e.data.data);
    self.postMessage(result);
  }
};

async function heavyProcessing(data) {
  // Simulate heavy processing
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(data.map(item => item * 2));
    }, 1000);
  });
}

Microtasks and the Event Loop

The execution order of Promise callbacks as microtasks in the event loop.

console.log('Script start');

setTimeout(() => console.log('setTimeout'), 0);

Promise.resolve()
  .then(() => console.log('Promise 1'))
  .then(() => console.log('Promise 2'));

console.log('Script end');

/*
Output order:
Script start
Script end
Promise 1
Promise 2
setTimeout
*/

Manually triggering the microtask queue:

function enqueueMicrotask(fn) {
  if (typeof queueMicrotask === 'function') {
    queueMicrotask(fn);
  } else {
    Promise.resolve().then(fn);
  }
}

enqueueMicrotask(() => console.log('This is a microtask'));

Promise Cancellation

Native Promises don't support cancellation, but it can be implemented via wrappers.

Using AbortController to cancel fetch requests:

const controller = new AbortController();

fetch('/api/data', { signal: controller.signal })
  .then(response => response.json())
  .catch(error => {
    if (error.name === 'AbortError') {
      console.log('Request canceled');
    }
  });

// Cancel the request
controller.abort();

Cancelable Promise wrapper:

function makeCancelable(promise) {
  let isCanceled = false;
  
  const wrappedPromise = new Promise((resolve, reject) => {
    promise.then(
      value => isCanceled ? reject({ isCanceled: true }) : resolve(value),
      error => isCanceled ? reject({ isCanceled: true }) : reject(error)
    );
  });
  
  return {
    promise: wrappedPromise,
    cancel() {
      isCanceled = true;
    }
  };
}

const { promise, cancel } = makeCancelable(fetch('/api/data'));

// Cancel the operation
cancel();

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

如果侵犯了你的权益请来信告知我们删除。邮箱: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 ☕.