阿里云主机折上折
  • 微信号
Current Site:Index > Promise.allSettled()

Promise.allSettled()

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

Basic Concept of Promise.allSettled()

ECMAScript 2020 (ES11) introduced the Promise.allSettled() method, which takes an array of Promises as input and returns a new Promise. This new Promise resolves only when all input Promises have settled (either fulfilled or rejected). Unlike Promise.all(), Promise.allSettled() does not terminate early if any Promise is rejected.

const promises = [
  Promise.resolve(1),
  Promise.reject('Error occurred'),
  Promise.resolve(3)
];

Promise.allSettled(promises)
  .then(results => {
    results.forEach((result, index) => {
      console.log(`Promise ${index}:`, result.status);
    });
  });

// Output:
// Promise 0: fulfilled
// Promise 1: rejected
// Promise 2: fulfilled

Return Value Structure

When the Promise returned by Promise.allSettled() resolves, it yields an array of result objects, each containing the following properties:

  • status: A string indicating the Promise's state ("fulfilled" or "rejected")
  • value: Present when status is "fulfilled," representing the resolved value
  • reason: Present when status is "rejected," representing the rejection reason
const promises = [
  Promise.resolve('Success'),
  Promise.reject(new Error('Failure')),
  Promise.resolve('Another success')
];

Promise.allSettled(promises)
  .then(results => {
    results.forEach(result => {
      if (result.status === 'fulfilled') {
        console.log('Value:', result.value);
      } else {
        console.log('Reason:', result.reason.message);
      }
    });
  });

// Output:
// Value: Success
// Reason: Failure
// Value: Another success

Differences from Promise.all()

Promise.all() and Promise.allSettled() exhibit significant differences in handling Promise collections:

  1. Behavioral Differences:

    • Promise.all() rejects immediately if any Promise rejects
    • Promise.allSettled() waits for all Promises to settle
  2. Result Differences:

    • Promise.all() resolves to an array of values
    • Promise.allSettled() resolves to an array of objects containing status information
// Promise.all() example
Promise.all([
  Promise.resolve(1),
  Promise.reject('Error'),
  Promise.resolve(3)
])
.catch(error => {
  console.log('Promise.all caught:', error); // Output: Promise.all caught: Error
});

// Promise.allSettled() example
Promise.allSettled([
  Promise.resolve(1),
  Promise.reject('Error'),
  Promise.resolve(3)
])
.then(results => {
  console.log('All settled:', results);
  /* Output:
  All settled: [
    { status: 'fulfilled', value: 1 },
    { status: 'rejected', reason: 'Error' },
    { status: 'fulfilled', value: 3 }
  ]
  */
});

Practical Use Cases

Promise.allSettled() is particularly useful in the following scenarios:

  1. Batch Request Processing: When multiple independent requests need to be sent, and failure of one should not affect others
async function fetchMultipleUrls(urls) {
  const requests = urls.map(url => 
    fetch(url)
      .then(response => response.json())
      .catch(error => ({ error: true, message: error.message }))
  );
  
  const results = await Promise.allSettled(requests);
  
  return results.map(result => {
    if (result.status === 'fulfilled') {
      return { data: result.value };
    } else {
      return { error: result.reason };
    }
  });
}

// Usage example
const urls = [
  'https://api.example.com/data1',
  'https://api.example.com/data2',
  'invalid-url'
];

fetchMultipleUrls(urls).then(results => {
  results.forEach((result, index) => {
    console.log(`URL ${index}:`, result.data || result.error);
  });
});
  1. Multi-field Form Validation: When all field validation results need to be collected without stopping at the first failure
function validateFields(fields) {
  const validations = fields.map(field => {
    return new Promise((resolve) => {
      // Simulate async validation
      setTimeout(() => {
        if (field.value.length >= field.minLength) {
          resolve({ ...field, valid: true });
        } else {
          resolve({ ...field, valid: false, error: `Minimum ${field.minLength} characters` });
        }
      }, Math.random() * 1000);
    });
  });

  return Promise.allSettled(validations)
    .then(results => results.map(result => result.value));
}

// Usage example
const formFields = [
  { name: 'username', value: 'john', minLength: 3 },
  { name: 'password', value: '123', minLength: 6 },
  { name: 'email', value: 'john@example.com', minLength: 5 }
];

validateFields(formFields).then(validationResults => {
  validationResults.forEach(result => {
    console.log(`${result.name}:`, result.valid ? 'Valid' : `Invalid - ${result.error}`);
  });
});

Error Handling Strategies

When using Promise.allSettled(), different strategies can be employed to handle rejected Promises:

  1. Filtering Out Failed Promises:
const promises = [
  Promise.resolve('Data 1'),
  Promise.reject('Error 1'),
  Promise.resolve('Data 2'),
  Promise.reject('Error 2')
];

Promise.allSettled(promises)
  .then(results => {
    const successfulResults = results
      .filter(result => result.status === 'fulfilled')
      .map(result => result.value);
    
    const errors = results
      .filter(result => result.status === 'rejected')
      .map(result => result.reason);
    
    console.log('Successes:', successfulResults);
    console.log('Errors:', errors);
  });

// Output:
// Successes: ["Data 1", "Data 2"]
// Errors: ["Error 1", "Error 2"]
  1. Retrying Failed Promises:
async function fetchWithRetry(url, retries = 3) {
  for (let i = 0; i < retries; i++) {
    try {
      const response = await fetch(url);
      return await response.json();
    } catch (error) {
      if (i === retries - 1) throw error;
      await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
    }
  }
}

async function fetchImportantData(urls) {
  const initialAttempts = urls.map(url => 
    fetch(url).then(r => r.json()).catch(() => null)
  );
  
  const initialResults = await Promise.allSettled(initialAttempts);
  
  const needsRetry = initialResults
    .filter((result, index) => result.status === 'rejected')
    .map((_, index) => urls[index]);
  
  const retryResults = await Promise.allSettled(
    needsRetry.map(url => fetchWithRetry(url))
  );
  
  // Merge results...
}

Performance Considerations

Although Promise.allSettled() waits for all Promises to settle, performance-sensitive scenarios require attention:

  1. Parallel Execution: All Promises still execute in parallel and do not become sequential due to allSettled()
  2. Memory Usage: A large number of concurrent Promises may cause memory pressure
  3. Timeout Control: Can be combined with Promise.race() to implement timeout mechanisms
function withTimeout(promise, timeout) {
  return Promise.race([
    promise,
    new Promise((_, reject) => 
      setTimeout(() => reject(new Error('Timeout')), timeout)
    )
  ]);
}

async function processTasks(tasks) {
  const promises = tasks.map(task => 
    withTimeout(task.execute(), task.timeout || 5000)
      .then(value => ({ task, status: 'fulfilled', value }))
      .catch(reason => ({ task, status: 'rejected', reason }))
  );
  
  const results = await Promise.allSettled(promises);
  
  // Process results...
}

Browser Compatibility and Polyfill

While modern browsers generally support Promise.allSettled(), older environments may require a polyfill:

if (!Promise.allSettled) {
  Promise.allSettled = function(promises) {
    return Promise.all(promises.map(p => 
      Promise.resolve(p).then(
        value => ({ status: 'fulfilled', value }),
        reason => ({ status: 'rejected', reason })
      )
    ));
  };
}

For TypeScript users, type definitions can be added:

interface PromiseFulfilledResult<T> {
  status: 'fulfilled';
  value: T;
}

interface PromiseRejectedResult {
  status: 'rejected';
  reason: any;
}

type PromiseSettledResult<T> = PromiseFulfilledResult<T> | PromiseRejectedResult;

declare global {
  interface PromiseConstructor {
    allSettled<T>(promises: Iterable<Promise<T>>): Promise<PromiseSettledResult<T>[]>;
  }
}

Integration with Other Async Patterns

Promise.allSettled() can be combined with other async patterns:

  1. With async/await:
async function processMultipleTasks(tasks) {
  const results = await Promise.allSettled(
    tasks.map(task => task.execute())
  );
  
  const report = {
    successCount: results.filter(r => r.status === 'fulfilled').length,
    failureCount: results.filter(r => r.status === 'rejected').length,
    errors: results
      .filter(r => r.status === 'rejected')
      .map(r => r.reason.message)
  };
  
  return report;
}
  1. With Generator Functions:
function* taskGenerator(count) {
  for (let i = 0; i < count; i++) {
    yield new Promise((resolve, reject) => {
      setTimeout(() => {
        Math.random() > 0.3 ? resolve(`Task ${i} completed`) : reject(`Task ${i} failed`);
      }, Math.random() * 1000);
    });
  }
}

async function runTasks(count) {
  const tasks = [...taskGenerator(count)];
  const results = await Promise.allSettled(tasks);
  
  results.forEach((result, i) => {
    console.log(`Task ${i}:`, 
      result.status === 'fulfilled' 
        ? result.value 
        : `Failed - ${result.reason}`
    );
  });
}

runTasks(5);

Practices in Complex Systems

In large-scale applications, Promise.allSettled() helps build more robust systems:

  1. Microservice Calls:
class ServiceOrchestrator {
  constructor(services) {
    this.services = services;
  }
  
  async gatherData(request) {
    const servicePromises = this.services.map(service => 
      service.fetchData(request)
        .then(data => ({ service: service.name, data }))
        .catch(error => ({ service: service.name, error }))
    );
    
    const results = await Promise.allSettled(servicePromises);
    
    return results.map(result => {
      if (result.status === 'fulfilled') {
        return { 
          service: result.value.service,
          status: 'success',
          data: result.value.data
        };
      } else {
        return {
          service: result.reason.service,
          status: 'error',
          error: result.reason.error
        };
      }
    });
  }
}

// Usage example
const orchestrator = new ServiceOrchestrator([
  { name: 'userService', fetchData: fetchUser },
  { name: 'productService', fetchData: fetchProducts },
  { name: 'inventoryService', fetchData: fetchInventory }
]);

orchestrator.gatherData({ userId: 123 }).then(report => {
  console.log('Service Report:', report);
});
  1. Data Aggregation:
async function aggregateDataSources(sources) {
  const dataPromises = sources.map(async source => {
    try {
      const rawData = await fetch(source.url);
      const data = await rawData.json();
      return {
        source: source.name,
        status: 'success',
        data: source.transform ? source.transform(data) : data
      };
    } catch (error) {
      return {
        source: source.name,
        status: 'error',
        error: error.message
      };
    }
  });
  
  const results = await Promise.allSettled(dataPromises);
  
  return {
    successful: results
      .filter(r => r.status === 'fulfilled' && r.value.status === 'success')
      .map(r => r.value),
    failed: results
      .filter(r => r.status === 'fulfilled' && r.value.status === 'error')
      .map(r => r.value)
  };
}

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

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