Promise 知识点总结

312 阅读8分钟

Promise 知识点总结

Promise 是 JavaScript 中用于处理异步操作的一种机制。它是一个对象,表示某个异步操作的最终结果 ,成功或失败 ;

Promise 的三种状态

(1) 对象的状态不受外界影响

Promise 对象通过自身的状态 ,来控制异步操作。它有三种状态

  • pending : 初始状态(进行中)
  • fulfilled : 操作成功,它调用.then 回调,例如 .then(onSuccess)
  • rejected: 操作失败,它调用.catch.then 的第二个参数(如果有)

注:fulfilledrejected合在一起称为resolved(已定型)

只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。

(2) 状态一旦发生改变就不会再变

Promise 的英文名称叫承诺 , 承诺一旦生效 , 将不在发生改变

这三种的状态的变化途径只有两种 :

  • 从“未完成” 到 “成功”
  • 从“未完成” 到 “失败”

new Promise(callback)

Promise 是一个对象,也是一个构造函数

  • 通常使用 new Promise 构造函数创建一个 Promise 对象 , 需要传入一个回调函数 callback 作为参数 , 该回调函数会立即执行
  • new Promise 执行的时候会给 callback 函数传入两个参数resolvereject
// 声明一个新的Promise对象,他的初始状态为pending
new Promise((resolve, reject) => {
  console.log("Promise 执行了"); // 立即执行
});   

resolve 和 reject 函数

这两个函数用于改变 Promise 的状态

  • resolve 在异步操作成功时调用 , 将Promise 的状态从"未完成"变为"成功" (即从 pending 变成 fulfilled)
  • reject 在异步操作失败时调用 , 将Promise 的状态从"未完成"变为"失败" (即从 pending 变成 rejected)

异步操作的结果 , 是作为参数传递出去的, 由 .then.catch 接收

可以使用简写 Promise.resolve 或者 Promise.reject 创建一个成功或者失败的 Promise 对象

const p_1 = new Promise((resolve, reject) => {
  return resolve("成功了");
  // return reject("失败了");
});

Promise.resolve("start").then(() => {}); // 简写

then 函数

  • then(success, failed)方法用于处理异步操作成功的情况 , 它接受两个参数:success是成功的回调函数 , failed是失败的回调函数

  • success接收resolve 传入的值作为参数 , failed接收reject 传入的值作为参数

    new Promise((resolve, reject) => {
      return resolve("成功了");
      // return reject("失败了");
    }).then(
      (res) => {
        console.log(res); // resolve触发
      },
      (res) => {
        console.log(res); // reject触发  | .then的第二参数可以省略,在开发中不常用
      }
    );
    
  • Promise 的 每个 .then 方法都会返回一个新的 Promise 对象 , 可以 then 之后还能继续调用then , 实现 链式调用 , 同样的 .catch也可以链式调用

  • then() 或者 catch() 的参数期望是函数 , 传入非函数则会发生值透传

    new Promise((resolve, reject) => {
      resolve("start");
    })
      // .then(1) // 非函数,值透传  | - 不常用
      .then((res) => {
        console.log(res);
      });
    

返回值处理

then 的回调函数内可以return值抛出错误 , 我们需要对这些返回值进行处理

  • 返回自身 => 报错
  • 返回已知状态的 Promise 对象 => 状态由返回的 Promise 对象决定
  • 没有 return , 相当于 return Promise.resolve(undefined)
  • return 任意值 ,【不包含 Promise.reject(throw new Error)】, 会被包装成一个状态为 fulfilled 的 Promise 对象 ,相当于resolve(任意值)
  • throw new Error 抛出错误 , Promise 的值状态变成 rejected, 相当于reject(错误)
let p = new Promise((resolve, reject) => {
  resolve("成功获取到数据");
})
  .then(() => {
    // return p; //返回自身 => 报错
    return Promise.resolve(9999); //返回已知状态的Promise对象
  })
  .then((res) => {
    console.log("then1", res);
    return 11; //  相当于 return Promise.resolve(11)
    //  return new Error("error-1"); //  这句也是返回任意值 ***
  })
  .then((res) => {
    console.log("then2", res); //相当于 return Promise.resolve(undefined)
  });

catch 函数

catch()方法用于捕获异步操作失败的情况 ,它接收一个回调函数作为参数 , 用来打印错误信息。

  • Promisereject时会被 catch 捕获
  • Promise 链中的 then/finall回调中抛出的异常会被 catch 捕获
new Promise((resolve, reject) => {
  resolve(1);
})
  .then((res) => {
    return new Error("error-1");   // 没抛错 , 进then
  })
  .then((res) => {
    console.log("then2", res);
    throw new Error("error-2");   // throw抛错 , 进catch
  })
  .catch((err) => {
    console.log("catch", err);
  });

分析 :

  1. Promise 新建后就会立即执行,resolve(1) 使 Promise 状态变为 fulfilled
  2. 执行第一个then , 注意 return new Error === Promise.resolve(new Error), 所以会进入.then 而不是 catch
  3. 执行第二个then , 输出 "then2,error-2" ; 此处使用 throw 抛出错误 , throw new Error === return Promise.reject
  4. catch 捕获异常 , 输出 "catch Error : error-2"

finally 函数

  • 无论 Promise 成功或失败 , finally 的回调函数都会在当前 Promise 完成后立即执行 , 而不是在链的末尾
  • finally 不接收前一个 Promise 的结果 , 通常不需参数
  • 无异常时 , finally 返回值不影响Promise链 ; 若抛出错误 , 将由最近的 catch 捕获
  • finally 专用于执行与结果无关的收尾操作 , 不涉及值的传递 , 如果需要处理 Promise 的结果 , 应该在 thencatch 中进行
Promise.resolve("start")
  .finally(() => {
    console.log("1"); // 1
    return "finally";
  })
  .then((res2) => {
    console.log("then", res2); // then  start
  })
  .finally(() => {
    throw new Error("error");
  })
  .catch((res3) => {
    console.log("catch", res3); // catch  error
  });

Promise 并发

Promise 类提供了四个静态方法来促进异步任务的并发 :

  • Promise.all() : 在所有传入的 Promise 都被兑现时兑现 ;在任意一个 Promise 被拒绝时拒绝。
  • Promise.allSettled() : 在所有的 Promise 都被敲定时兑现。
  • Promise.any() : 在任意一个 Promise 被兑现时兑现;仅在所有的 Promise 都被拒绝时才会拒绝。
  • Promise.race() : 在任意一个 Promise 被兑现时兑现;在任意一个的 Promise 被拒绝时拒绝。

async/await

async/await 语法用于处理异步操作 , async 关键字用于声明一个函数是异步的,await 关键字用于等待一个 Promise 的结果

async function funC_3() {
  let value_2 = await new Promise((resolve, reject) => {
    resolve("成功获取到数据");
  });
  console.log(value_2);
}
funC_3();

解析 : 给 value_2 赋值、输出 value_2 , 是在微任务队列中的

Promise.resolve().then(() => {
  new Promise((resolve, reject) => {
    resolve("成功获取到数据");
  }).then((value_2) => {
    console.log(value_2);
  });
});

这两种写法是等效的 , async/await 减少了嵌套 , 提高了代码的可读性和维护性,更推荐这种写法

总结 : 可以使用 then 方法接收异步操作成功的返回值 , 也可以使用 async/await 接收异步操作成功的返回值

js 任务调度

同步任务 : 在主线程上按顺序执行,必须等待前一个任务完成才能开始下一个。

异步任务 : 不直接在主线程上执行,而是先放入"任务队列"。只有当主线程空闲时,才会从任务队列中取出异步任务放到主线程中去执行 , 异步任务分为宏任务微任务

宏任务

包含整个脚本的初始执行、事件(如点击、键盘输入)、setTimeoutsetInterval、I/O、UI 渲染等 , 这些任务在事件循环的不同阶段执行。

微任务

是更为轻量级的任务,它们在当前宏任务结束后,但在下一个宏任务开始之前执行。典型的微任务包括:

Promise.then.catch等 、async/await 的后续操作 , Ajax请求MutationObserverprocess.nextTick( Node.js 环境特有)

JavaScript 事件循环 (Event Loop) 依次执行以下步骤:

  1. 主任务执行
  • 初始化执行上下文 :
    • 首次执行全局上下文中的代码 , 这是一个同步过程
    • 在这个阶段 , 变量声明、函数声明等被处理
  • 队列准备 :
    • 在执行过程中 , 如果遇到异步操作(如 setTimeout、Promise 等) , 相关任务会被添加到宏任务队列或微任务队列。
    • 宏任务队列示例 : Macro = [] , (此处用数组模拟队列做展示)
    • 微任务队列示例 : Micro = []
  • 同步代码执行结束后 , 当主线程空闲时 , 优选处理微任务
  1. 事件循环开始
  • 微任务调度

    • 宏任务执行完毕 , 立即 清空微任务队列
    • 执行过程中新产生的宏任务和微任务 , 分别加入各自的队列
    • 注意 :本轮执行过程中新添加的微任务也会在本轮清空过程中执行
  • 宏任务调度

    • 微任务执行完毕后 , 从宏任务队列中 取出一个宏任务执行
    • 执行过程中新产生的宏任务和微任务 , 分别加入各自的队列
    • 每执行完一个宏任务 , 就需要去清空微任务队列
  1. 循环迭代
    • 重复【微任务调度 - 宏任务调度】的过程 , 直到两个队列均为空 , 循环结束

案例一:

console.log("start!");
setTimeout(() => {
  console.log("TimeOut!");
}, 0);
Promise.resolve("Promise!").then((res) => {
  console.log("res!");
});
console.log("End!");

分析 :

  1. 主任务中的代码至上而下执行 , 输出 start!
  2. 遇到 setTimeout , 将它标记为"宏 1", 添加到宏任务队列中 , Macro=[宏 1]
  3. 遇到 Promise.then, 将它标记为"微 1", 添加到微任务队列中 , Micro=[微 1]
  4. 继续执行, 输出 console.log("End!");
  5. 主任务执行结束 , 去清空微任务队列 , 执行"微 1" , 输出 res!
  6. 去检查宏任务队列 , 执行"宏 1" , 输出 TimeOut!

案例二:

const one = () => Promise.resolve(" One! ");
async function myFunc() {
  console.log(" In Function! ");
  const res = await one();
  console.log(res);
}
console.log(" Before function! ");
myFunc();
console.log(" After function! ");

分析 :

  1. 主任务中的代码至上而下执行 , 输出 Before function!
  2. myFunc()函数被调用 , 运行函数体 , 输出 In Function!
  3. 函数体继续执行 , 当遇到 await 关键字时 , async 函数体中剩余的代码被暂停, 会运行在微任务中而不是一个常规任务!将它标记为"微 1", 添加到微任务队列中 , Micro=[微 1]
  4. 继续执行全局上下文 , 输出 After function!
  5. 主任务执行结束 , 去清空微任务队列 , 输出 One!

最后

Promise案例分析

模拟事件循环