阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > Promise错误处理机制

Promise错误处理机制

作者:陈川 阅读数:61353人阅读 分类: JavaScript

Promise错误处理的基本概念

ECMAScript 6引入的Promise对象为异步编程提供了更优雅的错误处理方式。Promise的错误处理机制主要通过then方法的第二个参数和catch方法来实现,它解决了传统回调函数中错误处理混乱的问题。

const promise = new Promise((resolve, reject) => {
  // 异步操作
  setTimeout(() => {
    const random = Math.random();
    if (random > 0.5) {
      resolve('成功');
    } else {
      reject(new Error('失败'));
    }
  }, 1000);
});

promise
  .then(
    result => console.log(result),
    error => console.error(error.message) // 错误处理
  );

then方法的错误处理

then方法接受两个参数:第一个是Promise成功时的回调函数,第二个是Promise失败时的回调函数。这种设计使得错误处理可以直接与对应的异步操作关联起来。

fetch('https://api.example.com/data')
  .then(
    response => {
      if (!response.ok) {
        throw new Error('网络响应不正常');
      }
      return response.json();
    },
    error => {
      console.error('请求失败:', error);
      return Promise.reject(error);
    }
  )
  .then(data => console.log(data));

catch方法的使用

catch方法是then(null, rejection)的语法糖,专门用于捕获Promise链中的错误。它可以捕获前面所有Promise中未被处理的错误。

someAsyncOperation()
  .then(result => {
    // 处理结果
    return processResult(result);
  })
  .then(processedResult => {
    // 进一步处理
    return saveResult(processedResult);
  })
  .catch(error => {
    console.error('操作链中发生错误:', error);
    // 可以返回一个默认值或重新抛出错误
    return defaultResult;
  });

Promise链中的错误传播

Promise链中的错误会一直向下传递,直到遇到一个错误处理函数。如果没有错误处理函数,错误会被静默忽略(在浏览器中可能会在控制台显示未捕获的错误)。

Promise.resolve()
  .then(() => {
    throw new Error('第一个错误');
  })
  .then(() => {
    console.log('这个then不会执行');
  })
  .catch(error => {
    console.error('捕获到错误:', error.message);
    throw new Error('从catch中抛出的新错误');
  })
  .catch(error => {
    console.error('捕获到第二个错误:', error.message);
  });

finally方法

finally方法无论Promise最终状态如何都会执行,适合用于清理工作。它不接收任何参数,也无法知道Promise是成功还是失败。

let isLoading = true;

fetchData()
  .then(data => {
    // 处理数据
  })
  .catch(error => {
    // 处理错误
  })
  .finally(() => {
    isLoading = false;
    console.log('请求结束,无论成功或失败');
  });

未捕获的Promise错误

未捕获的Promise错误不会导致脚本停止执行,但可能会在控制台显示警告。在现代JavaScript环境中,可以使用unhandledrejection事件来全局捕获这些错误。

window.addEventListener('unhandledrejection', event => {
  console.warn('未处理的Promise拒绝:', event.reason);
  event.preventDefault(); // 阻止默认行为(控制台错误)
});

// 未捕获的Promise拒绝
Promise.reject(new Error('这个错误会被全局捕获'));

Promise.all的错误处理

当使用Promise.all处理多个Promise时,如果其中一个Promise被拒绝,整个Promise.all会立即被拒绝。要单独处理每个Promise的错误,可以在传给Promise.all的数组中对每个Promise添加错误处理。

const promises = [
  fetch('/api/data1').catch(e => ({ error: e.message })),
  fetch('/api/data2').catch(e => ({ error: e.message })),
  fetch('/api/data3').catch(e => ({ error: e.message }))
];

Promise.all(promises)
  .then(results => {
    results.forEach(result => {
      if (result.error) {
        console.log('请求失败:', result.error);
      } else {
        console.log('请求成功:', result);
      }
    });
  });

Promise.race的错误处理

Promise.race返回第一个敲定的Promise(无论是兑现还是拒绝)。它的错误处理需要注意,因为第一个拒绝的Promise会导致整个Promise.race被拒绝。

const timeout = new Promise((_, reject) => {
  setTimeout(() => reject(new Error('请求超时')), 5000);
});

Promise.race([
  fetch('/api/data'),
  timeout
])
  .then(data => console.log('数据获取成功:', data))
  .catch(error => console.error('错误:', error.message));

async/await中的错误处理

async函数返回一个Promise,可以使用try/catch来处理错误,这使得异步代码的错误处理看起来像同步代码。

async function fetchData() {
  try {
    const response = await fetch('/api/data');
    if (!response.ok) {
      throw new Error('网络响应不正常');
    }
    const data = await response.json();
    console.log('获取的数据:', data);
    return data;
  } catch (error) {
    console.error('获取数据失败:', error);
    // 可以选择重新抛出错误或返回默认值
    throw error;
  }
}

// 调用async函数
fetchData().catch(e => console.error('外部捕获:', e));

错误边界模式

在复杂的Promise链中,可以使用错误边界模式来限制错误的传播范围,防止一个错误影响整个应用。

function withErrorBoundary(promise, fallback) {
  return promise.catch(error => {
    console.error('错误边界捕获:', error);
    return fallback;
  });
}

// 使用错误边界
withErrorBoundary(
  riskyOperation(),
  { default: 'value' }
)
  .then(result => {
    // 这里可以安全地使用result
    console.log('结果:', result);
  });

自定义错误类型

为不同的错误场景创建自定义错误类型,可以更精确地识别和处理错误。

class NetworkError extends Error {
  constructor(message) {
    super(message);
    this.name = 'NetworkError';
  }
}

class ValidationError extends Error {
  constructor(message) {
    super(message);
    this.name = 'ValidationError';
  }
}

async function validateAndFetch() {
  try {
    const response = await fetch('/api/data');
    if (!response.ok) {
      throw new NetworkError('网络请求失败');
    }
    const data = await response.json();
    if (!data.valid) {
      throw new ValidationError('数据验证失败');
    }
    return data;
  } catch (error) {
    if (error instanceof NetworkError) {
      console.error('网络问题:', error.message);
      // 重试逻辑
    } else if (error instanceof ValidationError) {
      console.error('数据问题:', error.message);
      // 显示用户友好的错误
    }
    throw error;
  }
}

Promise的错误处理最佳实践

  1. 总是返回Promise:在then和catch回调中,要么返回一个值,要么返回一个Promise,确保链式调用可以继续。
// 不好的做法 - 打破了Promise链
promise.then(result => {
  saveResult(result); // 没有return
});

// 好的做法
promise.then(result => {
  return saveResult(result); // 显式返回
});
  1. 避免嵌套Promise:使用链式调用而不是嵌套,可以提高代码可读性。
// 避免这样
getUser().then(user => {
  getPosts(user.id).then(posts => {
    // 处理posts
  });
});

// 应该这样
getUser()
  .then(user => getPosts(user.id))
  .then(posts => {
    // 处理posts
  });
  1. 合理使用catch:在Promise链的适当位置使用catch,而不是在每个then后面都加catch。
// 不必要地在每个then后加catch
operation1()
  .then(result1 => { /*...*/ })
  .catch(e => console.error(e))
  .then(result2 => { /*...*/ })
  .catch(e => console.error(e));

// 更好的方式 - 在链的末尾统一处理
operation1()
  .then(result1 => { /*...*/ })
  .then(result2 => { /*...*/ })
  .catch(e => console.error(e));

错误处理的常见陷阱

  1. 忘记返回Promise:在then回调中开始新的异步操作但忘记返回Promise,会导致后续的then回调在新操作完成前执行。
// 错误示例
getUser()
  .then(user => {
    fetchPosts(user.id); // 忘记return
  })
  .then(posts => {
    // posts会是undefined
    console.log(posts);
  });

// 正确做法
getUser()
  .then(user => {
    return fetchPosts(user.id); // 显式返回
  })
  .then(posts => {
    console.log(posts);
  });
  1. 吞掉错误:在catch回调中没有重新抛出错误或返回一个被拒绝的Promise,会导致错误被"吞掉"。
// 错误被吞掉
dangerousOperation()
  .catch(error => {
    console.error(error);
    // 没有重新抛出
  })
  .then(() => {
    // 这里会执行,就像没发生过错误一样
  });

// 正确做法 - 重新抛出错误
dangerousOperation()
  .catch(error => {
    console.error(error);
    throw error; // 或 return Promise.reject(error)
  });
  1. 混淆同步错误和异步错误:在Promise构造函数中,同步错误会被自动捕获并转换为拒绝的Promise,但在then回调中的同步错误需要手动捕获。
// Promise构造函数中的同步错误会被自动捕获
new Promise(() => {
  throw new Error('同步错误');
}).catch(e => console.error(e)); // 会被捕获

// then回调中的同步错误也会被捕获
Promise.resolve()
  .then(() => {
    throw new Error('then中的同步错误');
  })
  .catch(e => console.error(e)); // 会被捕获

// 但在普通函数中不会
function riskyFunction() {
  throw new Error('普通函数错误');
}

try {
  riskyFunction();
} catch (e) {
  console.error(e);
}

高级错误处理模式

  1. 重试机制:实现一个带有重试逻辑的Promise包装器。
function retry(promiseFactory, retries = 3, delay = 1000) {
  return new Promise((resolve, reject) => {
    const attempt = (n) => {
      promiseFactory()
        .then(resolve)
        .catch(error => {
          if (n <= 1) {
            reject(error);
          } else {
            console.log(`尝试失败,剩余 ${n - 1} 次重试...`);
            setTimeout(() => attempt(n - 1), delay);
          }
        });
    };
    attempt(retries);
  });
}

// 使用示例
retry(() => fetch('/api/unstable'), 5, 2000)
  .then(response => console.log('最终成功:', response))
  .catch(error => console.error('所有尝试失败:', error));
  1. 超时控制:为Promise添加超时功能。
function withTimeout(promise, timeoutMs, timeoutError = new Error('操作超时')) {
  let timeoutId;
  const timeoutPromise = new Promise((_, reject) => {
    timeoutId = setTimeout(() => reject(timeoutError), timeoutMs);
  });

  return Promise.race([promise, timeoutPromise]).finally(() => {
    clearTimeout(timeoutId);
  });
}

// 使用示例
withTimeout(fetch('/api/slow'), 3000)
  .then(response => console.log('响应:', response))
  .catch(error => console.error('错误:', error.message));
  1. 错误聚合:当需要处理多个独立操作时,收集所有错误而不仅仅是第一个错误。
function settleAll(promises) {
  return Promise.all(promises.map(p => 
    p.then(
      value => ({ status: 'fulfilled', value }),
      reason => ({ status: 'rejected', reason })
    )
  ));
}

// 使用示例
settleAll([
  Promise.resolve('成功1'),
  Promise.reject('失败1'),
  Promise.resolve('成功2'),
  Promise.reject('失败2')
]).then(results => {
  const successes = results.filter(r => r.status === 'fulfilled');
  const failures = results.filter(r => r.status === 'rejected');
  console.log('成功的:', successes);
  console.log('失败的:', failures);
});

Promise错误处理与事件循环

理解Promise错误处理与JavaScript事件循环的关系很重要。Promise的回调总是作为微任务执行,这意味着错误处理也会在微任务队列中执行。

console.log('脚本开始');

// 同步错误会立即抛出
// throw new Error('同步错误'); // 这会停止脚本执行

// 异步Promise错误
Promise.reject(new Error('异步错误')).catch(e => {
  console.log('捕获异步错误:', e.message);
});

setTimeout(() => {
  console.log('setTimeout回调');
  throw new Error('setTimeout中的错误'); // 这会冒泡到全局
}, 0);

Promise.resolve().then(() => {
  console.log('微任务1');
  throw new Error('微任务中的错误');
}).catch(e => {
  console.log('捕获微任务错误:', e.message);
});

console.log('脚本结束');

/*
输出顺序:
脚本开始
脚本结束
微任务1
捕获微任务错误: 微任务中的错误
捕获异步错误: 异步错误
setTimeout回调
(然后setTimeout中的错误会冒泡到全局)
*/

Node.js中的Promise错误处理

在Node.js环境中,Promise错误处理有一些特殊的考虑因素,特别是与EventEmitter和回调API的互操作。

const fs = require('fs').promises;
const util = require('util');
const stream = require('stream');

// 将回调式API转换为Promise
const readFile = util.promisify(fs.readFile);

// 处理多个可能失败的IO操作
async function processFiles(filePaths) {
  const results = [];
  
  for (const filePath of filePaths) {
    try {
      const content = await readFile(filePath, 'utf8');
      results.push({ filePath, content, status: 'success' });
    } catch (error) {
      results.push({ filePath, error: error.message, status: 'failed' });
      // 可以选择继续处理其他文件而不是立即退出
    }
  }
  
  return results;
}

// 处理流中的Promise错误
async function handleStreamWithPromise(stream) {
  const pipeline = util.promisify(stream.pipeline);
  
  try {
    await pipeline(
      stream,
      async function* (source) {
        for await (const chunk of source) {
          yield processChunk(chunk); // 假设processChunk可能抛出错误
        }
      },
      someWritableStream
    );
  } catch (error) {
    console.error('流处理失败:', error);
    // 清理资源
  }
}

浏览器与Node.js环境差异

在不同JavaScript环境中,未捕获的Promise错误的处理方式有所不同:

  1. 浏览器环境

    • 未捕获的Promise拒绝会触发unhandledrejection事件
    • 现代浏览器会在控制台显示警告
    • 不会导致脚本完全停止
  2. Node.js环境

    • 会触发unhandledRejection事件
    • 从Node.js 15开始,未处理的Promise拒绝会导致进程退出(可以通过flag禁用)
    • 可以使用process.on('unhandledRejection')全局捕获
// 浏览器中的全局捕获
window.addEventListener('unhandledrejection', event => {
  console.error('未处理的Promise拒绝:', event.reason);
  // event.preventDefault(); // 可以阻止默认的console.error
});

// Node.js中的全局捕获
process.on('unhandledRejection', (reason, promise) => {
  console.error('未处理的拒绝:', reason);
  // 应用特定的日志记录或清理
});

// 两种环境都适用的检测
let potentiallyUnhandledRejections = new Map();

window.addEventListener('unhandledrejection', event => {
  potentiallyUnhandledRejections.set(event.promise, event.reason);
});

window.addEventListener('rejectionhandled', event => {
  potentiallyUnhandledRejections.delete(event.promise);
});

setInterval(() => {
  potentiallyUnhandledRejections.forEach((reason, promise) => {
    console.error('可能未处理的拒绝:', reason);
    // 处理这些拒绝
  });
  potentiallyUnhandledRejections.clear();
}, 60000);

Promise错误处理与TypeScript

在TypeScript中使用Promise时,类型系统可以帮助更安全地处理错误。

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

interface ApiError {
  message: string;
  statusCode: number;
}

async function fetchUser(userId: number): Promise<User> {
  try {
    const response = await fetch(`/api/users/${userId}`);
    if (!response.ok) {
      const error: ApiError = {
        message: `HTTP错误 ${response.status}`,
        statusCode: response.status
      };
      throw error;
    }
    return await response.json() as User;
  } catch (error) {
    console.error('获取用户失败:', error instanceof Error ? error.message : error);
    throw error; // 重新抛出以保持类型安全
  }
}

// 使用
fetchUser(123)
  .then(user => console.log('用户:', user))
  .catch((error: ApiError | Error) => {
    if ('statusCode' in error) {
      console.error('API错误:', error.statusCode, error.message);
    } else {
      console.error('一般错误:', error.message);
    }
  });

Promise错误处理与函数式编程

结合函数式编程概念,可以创建更通用的错误处理工具函数。

// 高阶函数:包装一个函数,使其返回的Promise错误被特定方式处理
const withErrorHandling = (fn, errorHandler) => (...args) => {
  try {
    const result = fn(...args);
    return Promise.resolve(result).catch(errorHandler);
  } catch (error) {
    return Promise.reject(errorHandler(error));
  }
};

// 使用示例
const unsafeFetch = url => fetch(url).then(r => r.json());

const safeFetch = withErrorHandling(unsafeFetch, error => {
  console.error('请求失败:', error);
  return { data

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

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

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌