JS中的异步和Promise

在 JavaScript 中,异步是处理耗时操作(如网络请求、文件读取、定时器等)的一种机制,目的是避免阻塞主线程的执行。JavaScript 是单线程语言,它通过事件循环机制来处理异步操作,使程序能够执行耗时操作而不冻结用户界面或阻塞其他任务。

一、核心概念

  1. 单线程模型
    • JavaScript 是单线程的,这意味着它一次只能执行一个任务。如果一个任务运行时间过长,其他任务就会被阻塞,直到这个任务完成。
  2. 事件循环机制(Event Loop)
    • JavaScript 的异步实现依赖于事件循环机制。事件循环的作用是检查任务队列(存储异步任务的回调函数,其中又分为宏任务队列和微任务队列),当主线程执行完同步任务后,事件循环会从任务队列中取出异步任务的回调并执行(微任务优先级高于宏任务)。

了解更多请查看:JS中的单线程、事件循环、同步和异步

二、异步的目的

  • 异步编程的目的是为了让 JavaScript 在执行耗时操作时,可以继续执行其他任务,而不阻塞主线程。例如,在发起一个网络请求后,JavaScript 不会等待服务器的响应再继续执行后续代码,而是通过异步机制执行其他任务,直到网络请求完成,才会处理它的结果。

三、异步操作的执行流程

  • 当一个异步任务被发起时,它不会立即执行,而是会进入任务队列。JavaScript 引擎会先执行所有同步任务,执行完毕后,事件循环开始处理任务队列中的异步任务。
    • 同步任务:直接在主线程上依次执行。
    • 异步任务:被放入任务队列,等待事件循环调度,在主线程空闲时执行。

四、异步的主要场景

  • 网络请求:如 fetch()XMLHttpRequest 等网络操作。
  • 定时器:如 setTimeout()setInterval()
  • 文件读取:如 Node.js 的文件系统操作。
  • UI 渲染:在浏览器中,避免阻塞 UI 渲染。

五、怎么在JS中使用异步?

1. 回调函数(Callback):(最开始的,不推荐)

  • 最基础的异步处理方式,通过将一个函数作为参数传递给另一个函数,在异步任务完成后调用这个函数。

示例:

console.log('任务 1');
setTimeout(() => {
  console.log('异步任务:定时器到期');
}, 1000);
console.log('任务 2');

输出顺序:

任务 1
任务 2
异步任务:定时器到期

回调函数的问题是容易产生“回调地狱”,代码难以阅读和维护。

2. Promise

  • 为了解决回调地狱问题,ES6 引入了 Promise。它是一个表示异步操作最终完成(或失败)的对象。通过 .then().catch() 方法可以更优雅地处理异步任务。

(1)Promise的三种状态:

  • 待定(pending):初始 状态,既没有被兑现也没有被拒绝
  • 已兑现(fulfilled):意味着操作 成功 完成,并返回一个值
  • 已拒绝(rejected):意味着操作 失败 并返回一个错误原因

(2)使用Promise()构造函数来创建Promise对象

  • 作用:主要用于封装尚未支持Promise的基于回调的API
  • 语法:
new Promise((resolve,reject)=>{
   if(...){
       resolve(value); // 解决时调用
   }else{
       reject(reason); // 拒绝时调用
   }
})  
  • 参数:resolvereject

    • 传入 resolvevalue 参数可以是另一个 Promise 对象(但不能是自身,否则会被拒绝并抛出TypeError),在这种情况下,新构造的 Promise 对象的状态将“ 锁定 ”到传入的 Promise 对象(作为 resolution Promise 的一部分)。
    • 传入 resolvevalue 参数如果使用一个非 thenable (没有then方法)的值(基本类型,或一个没有 then 属性或 then 属性不可调用的对象),则该 Promise 对象会被立即以该值兑现
    • reject 的语义类似于 throw 语句,因此 reason 通常是一个 Error 实例。
    • throw 语句用于抛出用户自定义的异常。当前函数的执行将停止(throw 之后的语句不会被执行),并且控制权将传递给调用堆栈中第一个 catch 块。如果调用函数中没有 catch 块,则程序将终止。
    throw new Error('出错了');    //reject('出错了')
    
  • 返回值:当通过 new 关键字调用 Promise 构造函数时,它会返回一个 Promise 对象。

(3)典型的Promise流程

  1. 构造 Promise:创建 Promise 对象时,(resolve, reject)=>{}被立即同步调用,并接收 resolvereject 函数作为参数。
  2. 异步操作:执行器封装异步操作,通过回调调用 resolvereject 来传递结果或错误。
  3. 状态变化
    • 待定状态:Promise 初始化后,可能处于待定状态。
    • 已解决状态:调用 resolve 后,Promise 变为已解决,接收值。
    • 已拒绝状态:调用 reject 后,Promise 变为已拒绝,接收错误信息。
  4. 唯一性:一旦 resolvereject 被调用,Promise 状态不能改变,后续调用将被忽略。
  5. 错误处理:如果执行器抛出错误,Promise 会被拒绝,但如果已调用 resolvereject,则错误会被忽略。
  6. 异步调用处理程序:一旦 Promise 状态确定,会异步调用任何通过 thencatchfinally 注册的处理程序,传递最终值或错误。

(4)Promise链式调用:.then().catch().finally()

  • .then()

    • 语法then(onFulfilled, onRejected)

    • 参数:第一个参数是 Promise 兑现时的回调函数,第二个参数是 Promise 拒绝时的回调函数。

    • 返回值:每个 .then() 返回一个新生成的 Promise 对象,这个对象可被用于链式调用。在 .then() 中传递的回调函数(onFulfilledonRejected)的执行结果决定了返回的 Promise(p)的状态。如果处理函数:

      • 返回一个值:p 以该返回值作为其兑现值。
      • 抛出一个错误:p 抛出的错误作为其拒绝值。
      • 返回一个已兑现的 Promise 对象:p 以该 Promise 的值作为其兑现值。
      • 返回一个已拒绝的 Promise 对象:p 以该 Promise 的值作为其拒绝值。
      • 返回另一个待定的 Promise 对象:p 保持待定状态,并在该 Promise 对象被兑现/拒绝后立即以该 Promise 的值作为其兑现/拒绝值。
    • 即使 .then() 缺少返回 Promise 对象的回调函数,处理程序仍会继续到链的下一个链式调用。因此,在最终的 .catch() 之前,可以安全地省略每个链式调用中处理已拒绝状态的回调函数。

    • .then() 只有一个参数时,那么参数为兑现回调函数。

    • 异步优先级:属于微任务,执行优先级高于宏任务

  • .catch()

    • .catch()等同于.then(undefined, onRejected) ,即.then()的兑现回调函数为undefined,只处理拒绝情况,而不处理兑现(fulfilled)的情况。
  • .finally()

    • finally() 方法类似于调用 then(onFinally, onFinally),即兑换和拒绝回调函数是同一函数,函数的执行与状态无关。
    • 不同于 .then().finally() 的回调函数并不会接收到 Promise 的兑现值或拒绝原因。它主要用于执行与 Promise 状态无关的清理操作。

(5)Promise并发

  1. Promise.all()
    • 在所有传入的 Promise 都被兑现时兑现(返回值为所有Promise兑现值的数组);在任意一个 Promise 被拒绝时拒绝。
  2. Promise.allSettled()
    • 在所有的 Promise 都被敲定时兑现。
  3. Promise.any()
    • 在任意一个 Promise 被兑现时兑现;仅在所有的 Promise 都被拒绝时才会拒绝(返回值为所有Promise拒绝理由的数组)。
  4. Promise.race()
    • 在任意一个 Promise 被敲定时敲定。换句话说,在任意一个 Promise 被兑现时兑现;在任意一个的 Promise 被拒绝时拒绝。
  • 这些方法的参数一般是Promise数组,或可迭代对象。
  • 返回值是一个Promise对象。

(6)Promise.reject()Promise.resolve()

  • Promise.reject()返回以给定理由拒绝的Promise 对象。
  • Promise.resolve()返回以给定的值兑现的Promise 对象。

3. async 和 await

  • async/await 是基于 Promise 的语法糖,ES8 中引入,使异步代码看起来像同步代码,更加简洁明了。async 用于声明一个函数返回 Promise,await 用于等待异步操作的结果。
  • 当使用 async 时,await expression; 中的 expression 是要等待的 Promise 实例,其中的代码会同步执行,而 await 后面的代码相当于 .then() 的内容,因此是异步执行的。。

示例:

async function fetchData() {
  const data = await new Promise((resolve) => {
    setTimeout(() => resolve('数据已返回'), 1000);
  });
  console.log(data);  // 输出:数据已返回
}

fetchData();

六、不同异步操作的执行优先级

  • 微任务执行优先级高于宏任务
  • 微任务通常用于 Promise 的回调(即 then、catch 和 finally)和 MutationObserver 等。
  • 宏任务通常指的是事件处理、定时器(如 setTimeout 和 setInterval)、I/O 操作等。
  • 同级别的任务执行优先级满足FIFO(先进先出)
console.log("开始");

setTimeout(() => {
  console.log("宏任务 - setTimeout");
}, 0);

Promise.resolve().then(() => {
  console.log("微任务 - Promise");
});

(async () => {
  await Promise.resolve();
  console.log("微任务 - async/await");
})();

console.log("结束");

开始
结束
微任务 - Promise
微任务 - async/await
宏任务 - setTimeout

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值