阿里云主机折上折
  • 微信号
Current Site:Index > Asynchronous processing specification

Asynchronous processing specification

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

Asynchronous processing is a crucial concept in JavaScript programming, and proper conventions can enhance code readability and maintainability. Below are common asynchronous processing conventions and best practices.

Promise Usage Conventions

Promises are fundamental tools for handling asynchronous operations. Follow these rules when using them:

  1. Chaining: Avoid nested Promises; use .then() chaining instead.
// Correct example
fetchData()
  .then(processData)
  .then(saveData)
  .catch(handleError);

// Incorrect example
fetchData().then(data => {
  processData(data).then(result => {
    saveData(result).catch(err => {
      handleError(err);
    });
  });
});
  1. Error Handling: Every Promise chain must end with .catch().
getUserInfo()
  .then(updateUI)
  .catch(logError);  // Errors must be caught
  1. Return Full Promises: Always return the full Promise object inside .then().
// Correct
fetch('/api').then(response => {
  return response.json();
});

// Incorrect
fetch('/api').then(response => {
  response.json();  // Missing return
});

async/await Best Practices

ES2017 introduced async/await, making asynchronous code resemble synchronous code:

  1. Unified Error Handling: Wrap await calls with try-catch.
async function loadData() {
  try {
    const data = await fetchData();
    const processed = await processData(data);
    return processed;
  } catch (error) {
    console.error('Loading failed:', error);
    throw error;  // Re-throw for higher-level handling
  }
}
  1. Avoid Redundant await: Use parallel execution for non-dependent operations.
// Inefficient
const user = await getUser();
const posts = await getPosts();

// Optimized
const [user, posts] = await Promise.all([
  getUser(),
  getPosts()
]);
  1. Explicit async Marking: Asynchronous functions must be explicitly declared with async.
// Correct
async function fetchResource() { /*...*/ }

// Incorrect
function fetchResource() {  // Missing async
  return await someAsyncOp();
}

Callback Function Conventions

For scenarios requiring callback functions (e.g., traditional Node.js APIs):

  1. Error-First Principle: The first parameter of a callback must be the error.
fs.readFile('config.json', (err, data) => {
  if (err) return handleError(err);
  processConfig(data);
});
  1. Avoid Deep Nesting: Refactor to Promises if nesting exceeds 3 levels.
// Code needing refactoring
getUser(userId, (err, user) => {
  if (err) return console.error(err);
  getPermissions(user, (err, permissions) => {
    if (err) return console.error(err);
    checkAccess(permissions, (err, access) => {
      if (err) return console.error(err);
      // Business logic...
    });
  });
});

Event Emitter Conventions

When handling EventEmitter-type asynchronous events:

  1. Clean Up Listeners: Remove event listeners when no longer needed.
const emitter = new EventEmitter();

function listener(data) {
  console.log('Received:', data);
  emitter.off('data', listener);  // Clean up immediately after use
}

emitter.on('data', listener);
  1. Handle Error Events: Always listen for error events.
const stream = createReadStream('file.txt');
stream.on('error', (err) => {
  console.error('Stream error:', err);
});

Microtasks and Macrotasks

Understanding execution order is critical for complex asynchronous logic:

console.log('Script start');

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

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

console.log('Script end');

// Output order:
// Script start
// Script end
// Microtask
// Macrotask

Canceling Asynchronous Operations

Implementing cancelable asynchronous flows:

  1. AbortController Approach:
const controller = new AbortController();

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

// Cancel the request
controller.abort();
  1. Custom Cancel Token:
function createCancellablePromise(executor) {
  let rejectFn;
  const promise = new Promise((resolve, reject) => {
    rejectFn = reject;
    executor(resolve, reject);
  });
  
  return {
    promise,
    cancel: (reason) => {
      rejectFn(new Error(reason || 'Cancelled'));
    }
  };
}

Asynchronous State Management

Handling asynchronous operations in state management:

// Redux async action example
const fetchUser = (userId) => async (dispatch) => {
  dispatch({ type: 'USER_FETCH_START' });
  try {
    const response = await api.getUser(userId);
    dispatch({ type: 'USER_FETCH_SUCCESS', payload: response });
  } catch (error) {
    dispatch({ type: 'USER_FETCH_ERROR', error });
  }
};

// Vuex example
actions: {
  async loadPosts({ commit }) {
    commit('SET_LOADING', true);
    const posts = await fetchPosts();
    commit('SET_POSTS', posts);
    commit('SET_LOADING', false);
  }
}

Performance Optimization Strategies

Practical tips for improving asynchronous operation performance:

  1. Request Deduplication: Avoid duplicate requests.
const pendingRequests = new Map();

async function cachedFetch(url) {
  if (pendingRequests.has(url)) {
    return pendingRequests.get(url);
  }
  
  const promise = fetch(url).then(res => res.json());
  pendingRequests.set(url, promise);
  
  try {
    return await promise;
  } finally {
    pendingRequests.delete(url);
  }
}
  1. Batch Processing: Combine multiple asynchronous operations.
async function batchUpdateItems(items) {
  const BATCH_SIZE = 10;
  const batches = [];
  
  for (let i = 0; i < items.length; i += BATCH_SIZE) {
    batches.push(items.slice(i, i + BATCH_SIZE));
  }
  
  const results = [];
  for (const batch of batches) {
    const batchResults = await Promise.all(
      batch.map(item => updateItem(item))
    );
    results.push(...batchResults);
  }
  
  return results;
}

Testing Asynchronous Code

Writing reliable asynchronous test cases:

// Jest test example
describe('Asynchronous function testing', () => {
  // Callback style
  test('Callback execution', done => {
    fetchData((err, data) => {
      expect(err).toBeNull();
      expect(data).toHaveProperty('id');
      done();
    });
  });

  // Promise style
  test('Promise resolution', () => {
    return fetchData().then(data => {
      expect(data.status).toBe(200);
    });
  });

  // async/await style
  test('async/await test', async () => {
    const data = await fetchData();
    expect(data.items).toBeInstanceOf(Array);
  });

  // Timer mocking
  jest.useFakeTimers();
  test('Timer test', () => {
    const callback = jest.fn();
    delayedCallback(callback);
    
    jest.runAllTimers();
    
    expect(callback).toHaveBeenCalled();
  });
});

Browser vs. Node Differences

Differences in asynchronous handling across environments:

  1. Global Object Differences:
// Browser environment
window.setTimeout(() => {
  // Browser-specific APIs
}, 100);

// Node environment
global.setImmediate(() => {
  // Node-specific APIs
});
  1. Module Loading Differences:
// Node CommonJS
const fs = require('fs');
fs.readFile('file.txt', 'utf8', (err, data) => {
  // Callback handling
});

// Browser ES modules
import('./module.js').then(module => {
  module.doSomething();
});

Error Tracking and Debugging

Enhancing debuggability of asynchronous errors:

  1. Add Context Information:
async function wrappedFetch(url) {
  try {
    return await fetch(url);
  } catch (err) {
    err.context = { url, timestamp: Date.now() };
    throw err;
  }
}
  1. Long-Running Task Monitoring:
function withTimeout(promise, timeout, taskName) {
  return Promise.race([
    promise,
    new Promise((_, reject) => {
      setTimeout(() => {
        reject(new Error(`${taskName} timed out (${timeout}ms)`));
      }, timeout);
    })
  ]);
}

Concurrency Control

Managing the number of parallel asynchronous tasks:

class TaskQueue {
  constructor(concurrency) {
    this.concurrency = concurrency;
    this.running = 0;
    this.queue = [];
  }

  push(task) {
    this.queue.push(task);
    this.next();
  }

  next() {
    while (this.running < this.concurrency && this.queue.length) {
      const task = this.queue.shift();
      task().finally(() => {
        this.running--;
        this.next();
      });
      this.running++;
    }
  }
}

// Usage example
const queue = new TaskQueue(3);
urls.forEach(url => {
  queue.push(() => fetchUrl(url));
});

Evolution of Asynchronous Patterns

Historical perspective on asynchronous processing:

  1. Callback Hell Era:
// Typical 2013 code
readFile('a.txt', (err, a) => {
  if (err) return handle(err);
  readFile('b.txt', (err, b) => {
    if (err) return handle(err);
    writeFile('c.txt', a + b, (err) => {
      if (err) return handle(err);
      console.log('Done!');
    });
  });
});
  1. Promise Transition Period:
// Typical 2015 improvement
readFile('a.txt')
  .then(a => readFile('b.txt').then(b => [a, b]))
  .then(([a, b]) => writeFile('c.txt', a + b))
  .then(() => console.log('Done'))
  .catch(handle);
  1. Modern async/await:
// 2020+ modern style
async function concatFiles() {
  try {
    const a = await readFile('a.txt');
    const b = await readFile('b.txt');
    await writeFile('c.txt', a + b);
    console.log('Done');
  } catch (err) {
    handle(err);
  }
}

Utility Functions

Collection of commonly used asynchronous helpers:

  1. Retry Mechanism:
async function retry(fn, retries = 3, delay = 1000) {
  try {
    return await fn();
  } catch (err) {
    if (retries <= 0) throw err;
    await new Promise(res => setTimeout(res, delay));
    return retry(fn, retries - 1, delay * 2);
  }
}
  1. Progress Notification:
async function withProgress(promise, onProgress) {
  let progress = 0;
  const interval = setInterval(() => {
    progress = Math.min(progress + 10, 90);
    onProgress(progress);
  }, 100);

  try {
    const result = await promise;
    clearInterval(interval);
    onProgress(100);
    return result;
  } catch (err) {
    clearInterval(interval);
    throw err;
  }
}

Browser API Integration

Asynchronous patterns with browser APIs:

  1. Web Worker Communication:
// Main thread
const worker = new Worker('task.js');
worker.postMessage({ cmd: 'start', data: 42 });

worker.onmessage = (e) => {
  console.log('Result:', e.data);
};

// worker.js
self.onmessage = async (e) => {
  if (e.data.cmd === 'start') {
    const result = await heavyComputation(e.data.data);
    self.postMessage(result);
  }
};
  1. IndexedDB Transactions:
async function saveRecord(dbName, record) {
  return new Promise((resolve, reject) => {
    const request = indexedDB.open(dbName);
    
    request.onsuccess = (e) => {
      const db = e.target.result;
      const tx = db.transaction('store', 'readwrite');
      const store = tx.objectStore('store');
      
      const putRequest = store.put(record);
      putRequest.onsuccess = () => resolve();
      putRequest.onerror = (e) => reject(e.target.error);
    };
    
    request.onerror = (e) => reject(e.target.error);
  });
}

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

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