Promise 案例讲解大全

164 阅读10分钟

Promise 案例讲解大全

本篇文章汇总了一系列 Promise 实用案例分析 , 同时还包括了宏任务与微任务的具体实例解读 , 希望能为各位小伙伴掌握 Promise 提供参考!

Promise 的状态

案例一 : pending 状态
const promise = new Promise((resolve, reject) => {
  console.log(1);
  console.log(2);
});
promise.then(() => {
  console.log(3);
});
console.log(4);

分析 :

  1. new Promise 立即执行 , 输出 1 和 2
  2. 遇到 promise.then() , 由于 Promise 中没有resolve()或者reject() , 所以 promise 的状态会一直是 pending , promise.then()方法就不会执行 , 3 不会被输出
  3. 继续往下执行 , 输出 4

结果:

1
2
4
案例二 : fulfilled 状态
const p = new Promise((resolve, reject) => {
  console.log(0);
  return resolve(1);
}).then((res) => {
  console.log("then1", res);
  console.log("then1", p);
});

p.then(() => {
  console.log("then2", p);
});

分析 :

  1. Promise 新建后就会立即执行 , 输出 0
  2. 碰到 resolve(1) , Promise 状态由 pending 变为 fulfilled
  3. 执行.then() 中的第一行 , 输出 "then 1" ,
  4. 执行.then() 中的第二行 , 输出 then1 Promise {<fulfilled>: '1'}
  5. 执行 p.then , 输出 then2 Promise {<fulfilled>: '1'}

结果 :

0
then1 1
then1 Promise {<fulfilled>: '1'}
then2 Promise {<fulfilled>: '1'}

总结 : Promise 状态一旦发生改变就不会再变

案例三 : rejected 状态
new Promise((resolve, reject) => {
  reject("error");
}).catch((err) => {
  console.log("catch:", err);
});

分析:

  1. new Promise 立即执行 , 碰到 reject("error") , Promise 状态由 pending 变为 rejected
  2. catch 的参数接受到 reject 的返回值 , 输出 "catch:error"

结果:

 catch:error

返回值处理

案例一 : 值透传
new Promise((resolve, reject) => {
  resolve("start");
})
  .then(1)
  .then(Promise.resolve(2))
  .then((res) => {
    console.log(res);
  });

分析 :

由于前面两个 .then 的参数不是函数 , 导致发生值透传 , 所以resolve("start") 的值最终传递到了最后一个 .then

结果 : start

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

案例二 : 返回自身
const p = new Promise((resolve, reject) => {
  resolve(11);
}).then(() => {
  return p;
});

结果 :

Uncaught (in promise) TypeError: Chaining cycle detected for promise #<Promise>

总结 : .then.catch 返回的值如果是 Promise 本身 , 会造成死循环 , 导致报错

案例三 : 返回 Promise
let value_1 = new Promise((resolve, reject) => {
  resolve("成功获取到数据");
})
  .then(() => {
    return Promise.resolve(9999);
  })
  .then((data) => {
    console.log(data);
  });

结果 : 9999

总结 : 返回已知状态的 Promise 对象 => 状态由返回的 Promise 对象决定

案例四 : 非 Promise 的值
Promise.resolve("start")
  .then((res) => {
    console.log("then1", res);
    return 11;                 // 相当于 return Promise.resolve(11)
  })
  .then((res) => {
    console.log("then2", res); // 相当于 return Promise.resolve(undefined)
  })
  .then((res) => {
    console.log("then3", res);
    throw new Error("then3 error");  // 相当于 return Promise.reject(new Error('then3 error'))
  })
  .catch((err) => {
    console.log("catch", err);
    return "catch返回值";
  })
  .then((res) => {
    console.log("then3", res);
  });

分析 :

  1. Promise.resolve("start") 将 start 作为返回值传给下一个 .then()
  2. 执行第一个 .then() , 输出 "then1 start" , 遇到 return 11, 相当于 return Promise.resolve(11)
  3. 执行第二个 .then() , 输出 "then2 11" , 没有返回值 , 相等于 return Promise.resolve(undefined)
  4. 执行第三个 .then() , 输出 "then3 undefined" , 抛出错误 , 相当于执行了 return Promise.reject(new Error('then3 error'))
  5. 错误被 .catch()捕获 , 输出 "catch Error: then3 error" , 并 return 'catch 返回值' , 相当于 return Promise.resolve('catch 返回值')
  6. 最后的 .then() 会接收到返回值 , 输出 "then3 catch 返回值"

结果 :

then1 start
then2 11
then3 undefined
catch Error: then3 error
then3 catch返回值

总结 :

  • return 任意值 ,【不包含 Promise.reject(throw new Error)】, 会被包装成一个状态为 fulfilledPromise 对象 ,相当于resolve(任意值)
  • 没有 return , 相当于 return Promise.resolve(undefined)
  • throw new Error 抛出错误 , Promise 的值状态变成 rejected , 相当于reject(错误)
  • Promise 的 每个 .then方法都会返回一个新的 Promise 对象 , 可以 then 之后还能继续调用 then , 实现 链式调用 , 同样的 .catch 也可以链式调用

错误处理

案例一 : 返错与抛错的区别
new Promise((resolve, reject) => {
  resolve(1);
})
  .then((res) => {
    return new Error("error-1");
  })
  .then((res) => {
    console.log("then2", res);
    throw new Error("error-2");
  })
  .catch((err) => {
    console.log("catch", err);
  });

分析 :

  1. Promise 新建后就会立即执行 , resolve(1) 使 Promise 状态变为 fulfilled
  2. 执行第一个then , return new Error 会被隐式转换为 Promise.resolve(new Error), 所以会进入下一个 then
  3. 执行第二个then , 输出 "then2,error-2" ; throw new Error("error-2") 会中断 当前 then`的执行 , 创建一个 Promise.reject 对象
  4. catch 捕获异常 , 输出 "catch Error : error-2"

结果 :

then2 Error : error-1
catch Error : error-2

总结 :

  • 返回错误 : return new Error === Promise.resolve(new Error)
  • 抛出错误 : throw new Error === return Promise.reject()
  • 只有使用关键字 reject 或者 throw 抛出的错误才会被 catch 捕获
案例二 : then 的第二个参数
new Promise((resolve, reject) => {
  resolve();
})
  .then(
    (res) => {
      throw new Error("throw error");
    },
    (err) => {
      console.log("err", err);
    }
  )
  .catch((err) => {
    console.log("catch:", err);
  });

结果 : catch: Error: throw error

总结 :

  • then()方法用于处理异步操作成功的情况 , 它接受两个参数:第一个参数是成功的回调函数 , 第二个参数是失败的回调函数
  • Promise 而言 , 处理错误可以给 .then()方法传递第二个函数 , 但问题在于 .then()中的第二个函数无法捕获第一个函数出现的错误。而用 .catch()则能捕获到上层抛出的错误
案例三 : 多重 catch
new Promise((resolve, reject) => {
  reject();
})
  .catch((err) => {
    throw new Error("ee");
    // return  new Error("ee");
  })
  .then((res) => {
    console.log("then1:", res);
  })
  .catch((err) => {
    console.log("catch2:", err);
  });

分析 :

  1. new Promise 中 reject() , Promise 状态由 pending 转为 rejected
  2. 执行第一个 catch() , 抛错会进 catch || 未抛错会进 then
  3. catch() 捕获到异常 , 输出 catch2: Error: ee

结果 : catch2: Error: ee

总结 : .catch() 回调正常返回 , 则返回值会传递给与之关联的 Promise

案例四 : try/catch
new Promise((resolve, reject) => {
  try {
    throw new Error("An error occurred");
  } catch (error) {
    reject(error);
  }
}).catch((error) => {
  console.error(error); // 这里会打印错误信息:"An error occurred"
});

总结 : 在 try 中抛出的错误可以被 catch 捕获 ; 通常不会再 Promise 内部使用  try...catch , 而是直接抛错 , 此处仅做演示 ;

finally

案例一 : finally
Promise.resolve("start")
  .finally(() => {
    console.log(1);
    return "finally";
  })
  .then((res2) => {
    console.log("then", res2);
  })
  .finally(() => {
    throw new Error("error");
  })
  .catch((res3) => {
    console.log("catch", res3);
  });

分析:

  1. finally 的回调函数接会立即执行 , 它不接收前一个 Promise 的结果 , 输出 "1"
  2. return "finally" 会被忽略 , 因为 then 会接收到前一个 Promise.resolve("start") 传递的参数 , 所以输出 "then start"
  3. 第二个 finally 抛出错误 , 被 .catch() 捕获 , 输出 "catch Error: error "。

结果:

1
then start
catch Error: error

总结:

  • 无论 Promise 成功或失败 , finally 的回调函数都会在当前 Promise 完成后立即执行 , 而不是在链的末尾执行 ;
  • finally 不接收前一个 Promise 的结果 , 通常不需参数 ;
  • 无异常时 , finally 返回值不影响Promise链 ; 若抛出错误 , 将由最近catch 捕获
  • finally 专用于执行与结果无关的通用操作 , 不涉及值的传递 , 如果需要处理 Promise 的结果 , 应该在 thencatch 中进行 ;

Promise 并发

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

  • Promise.all() : 在所有传入的 Promise 都被兑现时兑现;在任意一个 Promise 被拒绝时拒绝。
  • Promise.allSettled() : 在所有的 Promise 都被敲定时兑现。
  • Promise.any() : 在任意一个 Promise 被兑现时兑现;仅在所 Promise 都被拒绝时才会拒绝。
  • Promise.race() : 在任意一个 Promise 被兑现时兑现;在任意 Promise 被拒绝时拒绝。
案例 & 分析 & 结果
function fn0() {
  return Promise.reject("failed");
}
function fn1() {
  return Promise.resolve(10);
}
function fn2() {
  return new Promise((resolve) => {
    setTimeout((_) => {
      resolve(20);
    }, 1000);
  });
}
function fn3() {
  return new Promise((resolve) => {
    setTimeout((_) => {
      resolve(30);
    }, 2000);
  });
}

// Promise.all 在所有传入的 Promise 都被兑现时兑现
Promise.all([fn1(), fn2(), fn3()]).then((result) => {
  console.log(result); // 2秒后返回 [10,20,30]
});

// Promise.all 在任意一个 Promise 被拒绝时拒绝
Promise.all([fn0(), fn1(), fn2()]).then(([data0, data1, data2]) => {
  console.log(data0, data1, data2); //立即返回 : failed
});

// Promise.allSettled 在所有的 Promise 都被敲定时兑现 , 都会返回一个数组
// 数组中的每个元素都是一个对象, 描述了一个 Promise 的状态和结果
Promise.allSettled([fn0(), fn3()]).then((result) => {
  console.log(result); // 2秒后返回 [{ status: "rejected", reason: "failed" },{ status: "fulfilled", value: 30 }];
});

// Promise.any() : 在任意一个 Promise 被兑现时兑现
Promise.any([fn1(), fn2(), fn3()]).then((result) => {
  console.log(result); // 立即返回10
});

// Promise.any() : 仅在所有的 Promise 都被拒绝时才会拒绝
Promise.any([fn0(), fn1(), fn2()]).then((result) => {
  console.log(result); // 立即返回 : 10
});

// Promise.race() : 在任意一个 Promise 被兑现时兑现
Promise.race([fn1(), fn2(), fn3()]).then((result) => {
  console.log(result); // 立即返回 : 10
});

// Promise.race() :在任意一个的 Promise 被拒绝时拒绝
Promise.race([fn0(), fn1(), fn2()]).then((result) => {
  console.log(result); // 立即返回 : failed
});

宏任务与微任务

案例一 : 微任务
const p_1 = new Promise((resolve, reject) => {
  console.log(1);
  resolve();
  console.log(2);
});
p_1.then(() => {
  console.log(3);
});
console.log(4);

分析 :

  1. 执行全局上下文 , 创建名为 p_1 的 Promise 对象 , 其状态为 pending , 输出 "1"
  2. 调用 resolve() , p_1 的状态变为 fulfilled
  3. 输出 "2"
  4. 当调用 p_1.then 并传入一个回调函数时,这个回调函数会被注册为一个微任务 , Micro=[微任务1]
  5. 输出 "4"
  6. 全局上下文执行完毕 , 检查微任务队列 , 输出 3

结果 : 1 2 4 3

案例二 : 宏任务与微任务
const p_2 = new Promise((resolve, reject) => {
  console.log(1);
  setTimeout(() => {
    console.log(2);
    resolve();
    console.log(3);
  });
});
p_2.then(() => {
  console.log(4);
});
console.log(5);

分析 :

  1. 执行全局上下文,创建名为 p_2 的 Promise 对象,状态为 pending,输出 "1"。
  2. 将 setTimeout 中的回调函数加入宏任务队列Macro=[宏任务1]
  3. p_2.then 注册回调,进入等待状态。
  • p_2.then  中的回调函数被创建并注册,但它不会立即执行 ;
  • 要等 p_2 的状态变变为 fulfilledrejected 时 , 才会进入微任务队列 ;
  1. 继续执行全局上下文,输出 "5"。
  2. 全局上下文执行结束,去检查微任务列表,由于 p_2 的状态尚未改变,微任务队列为空。
  3. 从宏任务队列中取出第一个任务执行。此时执行 setTimeout 中的代码:
  • 输出 "2"
  • 调用 resolve(),使得 p_2 的状态变为 fulfilledp_2.then 的回调被加入微任务队列,此时微任务队列为 Micro=[微任务1]
  • 输出 "3"`。
  1. 清空微任务队列,输出 "4"。

结果 : 1 5 2 3 4

案例三 : 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 接收异步操作成功的返回值

案例四 : Promise 与 async/await
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 one()
  • one() 返回一个已解析的 Promise , 状态为 fulfilled , 值为 "One! "
  • await 关键字使得 myFunc 函数内部的执行暂停 , (await 后面的代码会与 async 函数的剩余部分一起作为一个整体) 运行在微任务中!!! Micro=[微任务1]
  1. 输出 After function!
  2. 全局上下文执行结束 , 清空微任务队列 , 输出 One!

结果 :

Before function!
In Function!
After function!
One!
案例五 : 复杂的微任务难点分析
let p1 = new Promise((resolve, reject) => {
  resolve("start");
})
  .then(() => {
    console.log("1");
    let p2 = new Promise((resolve) => {
      console.log(6);
      resolve(5);
    })
      .then((data) => console.log(data))
      .then(() => {
        console.log(8);
      });
    return 1;
  })
  .then(() => {
    console.log(9);
    return 2;
  })
  .then((res) => {
    console.log(res);
  });

分析 :

  1. 创建 p1,状态变为 fulfilled,第一个 then 回调加入微任务队列,Micro=[微任务1]
  2. 执行微任务 1
    • 创建 p2 , 输出 "6"
    • resolve(5) , p2 的第一个 then 回调加入微任务队列 , Micro=[微任务2]
    • return 1 , p1 的第二个 then 回调 加入微任务队列 , Micro=[微任务2,微任务3]
  3. 执行微任务 2
    • 输出 5
    • p2 的第二个 then 回调 加入微任务队列 , Micro=[微任务3,微任务4]
  4. 执行微任务 3
    • 输出 9
    • p1 的第三个 then 回调 加入微任务队列 , Micro=[微任务4,微任务5]
  5. 执行微任务 4 , 输出 8
  6. 执行微任务 5 , 输出 2 ; 执行完毕 ;
案例六 练习题

此案例留给小伙伴自行分析 ~

题目一:

var a = "我是同步代码";
setTimeout(function () {
  Promise.resolve().then(function () {
    console.log("我是第一个宏任务中的微任务");
  });
  console.log("我是宏任务1");
  setTimeout(function () {
    console.log("我是第一个宏任务中的宏任务");
  });
});
setTimeout(function () {
  console.log("我是宏任务2");
});
Promise.resolve().then(function () {
  console.log("我是微任务");
});
console.log(a);