JavaScript开发下对于异步函数的理解(解决请求并未及时更新数据问题)

 原有代码

const saveCourse = () => {
  // 找到对应的 courseTypeId
  const selectedType = courseTypes.value.find(type => type.name === currentCourse.value.type);
  if (selectedType) {
    // 将 courseTypeId 赋值给 currentCourse
    currentCourse.value.courseTypeId = selectedType.id;
  } else {
    // 如果没有找到对应的类型,提示用户或者处理错误
    console.log('未找到对应的课程类型');
    return;
  }
  // 清空 ImageUrl
  currentCourse.value.ImageUrl = "";
  // 打印 currentCourse 看看数据结构
  console.log(currentCourse.value);
  // 调用接口保存数据
  courseApi.saveOrUpdate(currentCourse.value).then(res => {
    console.log(res.data);
  }).catch(err => console.log(err));
  selectAllData();
  selectAllData();
  // 关闭对话框
  isDialogOpen.value = false;
};

调用两次才更新数据 

在这种写法中,courseApi.saveOrUpdate(currentCourse.value) 返回的是一个 Promise,你使用 .then() 来处理异步操作成功的结果,使用 .catch() 来处理失败的情况。然而,存在几个问题:

  • 顺序问题:虽然 .then() 是异步的,但它并没有等待前面的 Promise 完成。在 selectAllData() 和 selectAllData() 的调用之间,可能已经进行了后续的代码执行,而实际上数据还没有更新。这会导致你在数据更新之前就关闭了对话框,甚至在 UI 上显示错误的结果。

  • 回调地狱:当你有多个异步请求时,回调会嵌套起来,造成代码难以阅读和维护。假设你再添加一个请求,可能会导致 .then() 和 .catch() 的嵌套层数不断增加,代码变得很难跟踪和调试。

 

 现在代码

const saveCourse = async () => {
  const selectedType = courseTypes.value.find(type => type.name === currentCourse.value.type);
  if (selectedType) {
    currentCourse.value.courseTypeId = selectedType.id;
  } else {
    console.log('未找到对应的课程类型');
    return;
  }
  currentCourse.value.ImageUrl = "";
  try {
    const res = await courseApi.saveOrUpdate(currentCourse.value);
    console.log(res.data);

  } catch (err) {
    console.log(err);
  }
  selectAllData();
  // 关闭对话框
  isDialogOpen.value = false;
};

在第二种写法中,使用了 async 和 awaitasync 使得函数变成异步函数,await 使得我们能够像写同步代码一样处理异步操作。这样写的优势有:

  • 代码更清晰async 和 await 将异步操作“同步化”,使得异步代码的结构更接近顺序执行的代码。即使是异步操作,也能按照从上到下的顺序执行,不需要层层嵌套,使得代码可读性更强。

  • 避免回调地狱:异步操作的执行不再需要通过 .then() 和 .catch() 来进行嵌套,而是通过 try-catch 来捕获错误。这个简化了错误处理,使得代码结构更加简洁。

  • 等待操作完成后再执行后续逻辑await 会暂停函数的执行,直到 Promise 返回结果或抛出错误。在此期间,后续的代码不会执行。这保证了我们在更新数据后,才会执行 selectAllData(),并且在数据更新之前不会关闭对话框。

 

异步的优势与对比

1. 可读性和可维护性
  • .then() 和 .catch():如果有多个异步请求,你会面临越来越深的嵌套,代码会变得很复杂。随着异步操作的增多,嵌套会形成一个“回调地狱”(callback hell),使得代码难以理解和维护。

    比如,若有多个 then() 层,代码可读性就会大大下降,每次添加新请求时都要将 then 继续嵌套进去,导致每个操作之间的逻辑变得不清晰,调试也变得困难。

  • async/await:使用 async/await 时,异步操作的写法变得像同步代码一样清晰,逻辑更易理解。这种方式简化了错误处理,使得我们可以通过 try-catch 语法捕获错误并处理,避免了多个 catch 的嵌套。

    例如,当我们调用 await 时,它会暂停执行,直到异步操作完成,这使得代码执行流程非常直观。

2. 顺序和依赖性
  • .then() 和 .catch():在 .then() 语法中,你不能轻易控制异步请求的顺序,尤其是当多个异步请求之间有依赖时。虽然你可以通过链式调用实现顺序执行,但代码的结构和逻辑会变得复杂。

  • async/await:使用 await 后,你可以保证前一个异步操作完成之后才会执行下一个操作。这确保了数据的更新是在 selectAllData() 调用之前完成的,避免了潜在的 race conditions(竞态条件)和数据不一致的问题。

3. 错误处理

  • .then() 和 .catch():这种方式需要你在每个异步请求后处理 .catch() 来捕获错误。对于多个异步请求,你需要为每个 Promise 添加单独的错误处理逻辑,容易遗漏错误处理。

  • async/await:你可以统一使用 try-catch 来捕获整个函数中的所有异步操作的错误,使得错误处理变得更加集中和清晰。

总结

  • 使用 .then() 和 .catch():适用于简单的异步操作,但当异步操作复杂时,会导致代码的可读性差,错误处理繁琐,且容易引发“回调地狱”。

  • 使用 async/await:使得异步操作的代码看起来更像同步代码,易于理解,错误处理更加集中。await 确保异步操作按顺序执行,避免了潜在的顺序问题和竞态条件。

异步编程的优势

  • 提高效率:避免了阻塞主线程,提高程序的响应速度。
  • 非阻塞性:异步操作不会影响程序的其余部分运行,特别是在进行网络请求或IO操作时,异步编程使得应用能够处理更多并发操作。

在复杂的异步操作中,async/await 让代码更加简洁、可维护,避免了回调嵌套和错误处理的复杂性。

那比如有四个请求我该怎么写,就是四个请求都需要await吗

如果你有多个异步请求需要执行,并且它们之间没有依赖关系(即它们可以并行执行),你可以选择并行执行这些请求,或者如果它们有顺序依赖,就需要依次 await。下面是两种常见的处理方式:

1. 请求之间没有依赖关系(并行执行)

如果四个请求是相互独立的,不依赖于其他请求的结果,你可以通过 Promise.all() 来并行执行所有请求。这样可以提高性能,因为这些请求是并行执行的,而不是按顺序逐个等待。

const saveCourse = async () => {
  const selectedType = courseTypes.value.find(type => type.name === currentCourse.value.type);
  if (selectedType) {
    currentCourse.value.courseTypeId = selectedType.id;
  } else {
    console.log('未找到对应的课程类型');
    return;
  }

  currentCourse.value.ImageUrl = "";
  
  try {
    // 并行执行四个请求
    const [res1, res2, res3, res4] = await Promise.all([
      courseApi.saveOrUpdate(currentCourse.value), // 第一个请求
      courseApi.getCourseDetails(currentCourse.value.id), // 第二个请求
      courseApi.getCourseReviews(currentCourse.value.id), // 第三个请求
      courseApi.getCourseInstructor(currentCourse.value.id) // 第四个请求
    ]);
    
    // 处理每个请求的响应
    console.log(res1.data); // 第一个请求的响应
    console.log(res2.data); // 第二个请求的响应
    console.log(res3.data); // 第三个请求的响应
    console.log(res4.data); // 第四个请求的响应
    
    // 执行其他操作,比如更新 UI 状态
    selectAllData();

  } catch (err) {
    console.log(err); // 捕获任何一个请求失败的错误
  }

  // 关闭对话框
  isDialogOpen.value = false;
};
解释:
  • Promise.all() 接收一个数组,数组中的每个元素都是一个 Promise
  • await Promise.all([请求1, 请求2, 请求3, 请求4]) 会等待所有的 Promise 完成,如果所有请求都成功,会返回一个包含每个请求响应的数组 [res1, res2, res3, res4]
  • 如果任何一个请求失败,Promise.all() 会立即拒绝并抛出错误。
为什么使用 Promise.all()
  • Promise.all() 可以并行执行多个请求,提高了性能。相比于逐个 await,可以在一个请求未完成时开始下一个请求,减少等待时间。
  • 如果这些请求相互独立,并且你需要等待它们都完成再继续执行后续操作,Promise.all() 是一个很好的选择。

2. 请求之间有依赖关系(按顺序执行)

如果四个请求之间有依赖关系,即某个请求的结果需要作为下一个请求的输入,那么就需要依次 await,确保请求按顺序执行。

const saveCourse = async () => {
  const selectedType = courseTypes.value.find(type => type.name === currentCourse.value.type);
  if (selectedType) {
    currentCourse.value.courseTypeId = selectedType.id;
  } else {
    console.log('未找到对应的课程类型');
    return;
  }

  currentCourse.value.ImageUrl = "";

  try {
    // 按顺序执行四个请求,依赖前一个请求的结果
    const res1 = await courseApi.saveOrUpdate(currentCourse.value); // 第一个请求
    console.log(res1.data); // 处理第一个请求的结果

    const res2 = await courseApi.getCourseDetails(res1.data.courseId); // 第二个请求,依赖第一个请求的结果
    console.log(res2.data); // 处理第二个请求的结果

    const res3 = await courseApi.getCourseReviews(res2.data.courseId); // 第三个请求,依赖第二个请求的结果
    console.log(res3.data); // 处理第三个请求的结果

    const res4 = await courseApi.getCourseInstructor(res3.data.courseId); // 第四个请求,依赖第三个请求的结果
    console.log(res4.data); // 处理第四个请求的结果

    // 执行其他操作,比如更新 UI 状态
    selectAllData();

  } catch (err) {
    console.log(err); // 捕获任何一个请求失败的错误
  }

  // 关闭对话框
  isDialogOpen.value = false;
};

 

解释:
  • 在这个示例中,每个请求都依赖于上一个请求的结果。你必须等待第一个请求完成,才能开始第二个请求,依此类推。
  • 每个请求都使用 await,确保前一个请求完成后才会执行下一个请求。
为什么要按顺序执行请求?
  • 如果请求之间有数据依赖关系(即某个请求的结果是下一个请求的输入),就必须按照顺序执行。
  • 例如,第一个请求可能返回一个课程的 ID,第二个请求需要使用这个 ID 获取课程的详情。此时不能并行执行,因为第二个请求需要第一个请求的结果。

总结

  • 并行执行请求(Promise.all():如果请求之间没有依赖关系,可以使用 Promise.all() 并行执行所有请求。这样可以节省时间,避免等待一个请求完成后再执行下一个请求。
  • 顺序执行请求(await:如果请求之间有依赖关系,必须按顺序执行,这时就需要使用多个 await,保证前一个请求完成后再开始下一个请求。

选择合适的方式:

  • 无依赖的请求:使用 Promise.all() 可以提高性能。
  • 有依赖的请求:必须使用 await 保证顺序执行,确保数据的准确性。

async 和 await 简化 Promise 的使用:

  • async:声明一个异步函数,该函数内部默认会返回一个 Promise
  • await:等待一个 Promise 完成,并返回它的结果。await 只能在 async 函数内部使用。
 
 

const fetchData = async () => {
  const result1 = await new Promise(resolve => setTimeout(() => resolve("数据1"), 1000));
  console.log(result1); // 数据1

  const result2 = await new Promise(resolve => setTimeout(() => resolve("数据2"), 1000));
  console.log(result2); // 数据2
};

fetchData();
 

总结:

Promise 是 JavaScript 处理中长时间运行的异步操作(如网络请求、定时器等)的核心机制。它提供了一种更加优雅和可控的方式来处理异步代码,使代码更加清晰,易于维护。async 和 await 的引入进一步简化了 Promise 的使用,允许开发者以同步的方式编写异步代码,避免了回调地狱的出现。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值