阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > then()和catch()方法

then()和catch()方法

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

then()方法的基本用法

Promise对象的then()方法是处理异步操作成功状态的核心方法。它接收两个可选参数:onFulfilled和onRejected回调函数,分别用于处理Promise的解决(fulfilled)和拒绝(rejected)状态。

const promise = new Promise((resolve, reject) => {
  setTimeout(() => resolve('操作成功'), 1000);
});

promise.then(
  value => {
    console.log(value); // "操作成功"
  },
  error => {
    console.error(error); // 不会执行
  }
);

then()方法返回一个新的Promise对象,这使得链式调用成为可能。每个then()回调的返回值会成为下一个then()回调的参数:

fetch('/api/data')
  .then(response => response.json())
  .then(data => {
    console.log('解析后的数据:', data);
    return processData(data);
  })
  .then(processedData => {
    console.log('处理后的数据:', processedData);
  });

catch()方法的工作原理

catch()方法是专门用于处理Promise链中错误的便捷方法,它实际上是then(null, onRejected)的语法糖:

fetch('/api/invalid-url')
  .then(response => response.json())
  .catch(error => {
    console.error('请求失败:', error);
    return { defaultData: true }; // 提供默认值
  })
  .then(data => {
    console.log('最终使用的数据:', data);
  });

catch()会捕获它之前所有then()中抛出的错误,包括同步错误和异步错误:

new Promise((resolve, reject) => {
  resolve('初始值');
})
  .then(value => {
    throw new Error('人为错误');
  })
  .catch(error => {
    console.error('捕获到错误:', error.message); // "人为错误"
  });

then()和catch()的链式调用

Promise链的强大之处在于可以连续调用多个then()和catch(),形成清晰的异步操作流程:

function getUser(id) {
  return fetch(`/users/${id}`)
    .then(response => {
      if (!response.ok) throw new Error('用户不存在');
      return response.json();
    });
}

getUser(123)
  .then(user => {
    console.log('获取用户成功:', user);
    return fetch(`/posts?userId=${user.id}`);
  })
  .then(posts => {
    console.log('获取用户帖子:', posts);
    return Promise.all(posts.map(post => 
      fetch(`/comments?postId=${post.id}`)
    ));
  })
  .catch(error => {
    console.error('获取用户数据过程中出错:', error);
    return { user: null, posts: [], comments: [] };
  })
  .then(finalResult => {
    console.log('最终结果:', finalResult);
  });

错误处理的优先级

在Promise链中,错误会沿着链向下传播,直到被catch()捕获。如果中间有then()提供了onRejected回调,错误会在那里被处理:

Promise.reject(new Error('初始错误'))
  .then(
    value => console.log('不会执行'),
    error => {
      console.log('第一个错误处理:', error.message);
      throw new Error('第二个错误');
    }
  )
  .catch(error => {
    console.log('捕获第二个错误:', error.message);
  });

then()回调的返回值

then()回调的返回值决定了链中下一个Promise的状态:

  1. 返回普通值:Promise会以该值解决
  2. 返回Promise:等待该Promise解决
  3. 抛出错误:Promise会被拒绝
Promise.resolve(1)
  .then(x => {
    console.log(x); // 1
    return x + 1;
  })
  .then(x => {
    console.log(x); // 2
    return Promise.resolve(x * 2);
  })
  .then(x => {
    console.log(x); // 4
    throw new Error('测试错误');
  })
  .catch(e => {
    console.error(e.message); // "测试错误"
    return 8;
  })
  .then(x => {
    console.log(x); // 8
  });

微任务队列与执行顺序

then()和catch()注册的回调会被放入微任务队列,在当前宏任务执行完后立即执行:

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

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

Promise.resolve()
  .then(() => console.log('Promise1'))
  .then(() => console.log('Promise2'));

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

// 输出顺序:
// 脚本开始
// 脚本结束
// Promise1
// Promise2
// setTimeout

实际应用中的常见模式

  1. 并行请求与顺序处理
const fetchUser = fetch('/user').then(r => r.json());
const fetchPosts = fetch('/posts').then(r => r.json());

Promise.all([fetchUser, fetchPosts])
  .then(([user, posts]) => {
    console.log('用户和帖子都加载完成');
    return processCombinedData(user, posts);
  })
  .catch(error => {
    console.error('加载失败:', error);
  });
  1. 超时控制
function fetchWithTimeout(url, timeout = 5000) {
  return Promise.race([
    fetch(url),
    new Promise((_, reject) => 
      setTimeout(() => reject(new Error('请求超时')), timeout)
  ]);
}

fetchWithTimeout('/api/data')
  .then(response => response.json())
  .catch(error => {
    if (error.message === '请求超时') {
      console.error('请求花费时间过长,请重试');
    } else {
      console.error('其他错误:', error);
    }
  });
  1. 重试机制
function fetchWithRetry(url, retries = 3) {
  return fetch(url).catch(error => {
    if (retries <= 0) throw error;
    console.log(`重试剩余次数: ${retries}`);
    return fetchWithRetry(url, retries - 1);
  });
}

fetchWithRetry('/unstable-api')
  .then(response => console.log('最终成功'))
  .catch(error => console.error('所有重试失败'));

Promise链中的finally()

虽然不是then()和catch()的直接部分,但finally()经常与它们配合使用,无论Promise最终状态如何都会执行:

let isLoading = true;

fetch('/api/data')
  .then(response => response.json())
  .then(data => {
    console.log('数据:', data);
  })
  .catch(error => {
    console.error('错误:', error);
  })
  .finally(() => {
    isLoading = false;
    console.log('请求结束,无论成功失败');
  });

then()方法的参数可选性

then()的两个参数都是可选的,如果省略onRejected,错误会继续向下传递:

Promise.reject(new Error('测试'))
  .then(() => console.log('不会执行'))
  .then(
    () => console.log('也不会执行'),
    // 这里没有提供onRejected
  )
  .catch(error => console.error('最终捕获:', error.message));

如果省略onFulfilled,值会直接传递给下一个then():

Promise.resolve('值')
  .then(undefined, error => console.error(error))
  .then(value => console.log('传递的值:', value)); // "值"

在async/await中的等效写法

虽然async/await提供了更直观的语法,但底层仍然基于Promise的then()和catch():

async function loadData() {
  try {
    const response = await fetch('/api/data');
    const data = await response.json();
    console.log('数据加载成功:', data);
    return data;
  } catch (error) {
    console.error('加载失败:', error);
    throw error; // 可以选择继续抛出或处理
  }
}

// 等同于
function loadDataEquivalent() {
  return fetch('/api/data')
    .then(response => response.json())
    .then(data => {
      console.log('数据加载成功:', data);
      return data;
    })
    .catch(error => {
      console.error('加载失败:', error);
      throw error;
    });
}

then()与catch()的性能考虑

  1. 每个then()调用都会创建新的微任务,过多的链式调用可能影响性能
  2. 错误处理应尽可能靠近可能出错的代码,避免不必要的catch()
  3. 在循环中使用Promise链时要注意内存使用
// 不推荐的深层嵌套
fetch('/step1')
  .then(r1 => fetch(`/step2/${r1.id}`)
    .then(r2 => fetch(`/step3/${r2.id}`)
      .then(r3 => console.log(r3))
    )
  );

// 更清晰的扁平结构
fetch('/step1')
  .then(r1 => fetch(`/step2/${r1.id}`))
  .then(r2 => fetch(`/step3/${r2.id}`))
  .then(r3 => console.log(r3));

浏览器兼容性与polyfill

虽然现代浏览器都支持Promise,但在旧环境中可能需要polyfill:

<script src="https://cdn.jsdelivr.net/npm/promise-polyfill@8/dist/polyfill.min.js"></script>
<script>
// 现在可以安全使用Promise
Promise.resolve('测试')
  .then(value => console.log(value))
  .catch(error => console.error(error));
</script>

then()与回调地狱的对比

Promise的then()方法有效解决了传统回调嵌套的问题:

// 回调地狱
getData(function(a) {
  getMoreData(a, function(b) {
    getEvenMoreData(b, function(c) {
      console.log('最终结果:', c);
    }, errorCallback);
  }, errorCallback);
}, errorCallback);

// 使用Promise then()
getData()
  .then(a => getMoreData(a))
  .then(b => getEvenMoreData(b))
  .then(c => console.log('最终结果:', c))
  .catch(errorCallback);

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

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

前端川

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