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);
分析 :
- new Promise 立即执行 , 输出 1 和 2
- 遇到 promise.then() , 由于 Promise 中没有resolve()或者reject() , 所以 promise 的状态会一直是 pending , promise.then()方法就不会执行 , 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);
});
分析 :
- Promise 新建后就会立即执行 , 输出 0
- 碰到 resolve(1) , Promise 状态由 pending 变为 fulfilled
- 执行.then() 中的第一行 , 输出 "then 1" ,
- 执行.then() 中的第二行 , 输出
then1 Promise {<fulfilled>: '1'}
- 执行 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);
});
分析:
- new Promise 立即执行 , 碰到 reject("error") , Promise 状态由 pending 变为 rejected
- 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);
});
分析 :
- Promise.resolve("start") 将 start 作为返回值传给下一个
.then()
- 执行第一个
.then()
, 输出 "then1 start" , 遇到 return 11, 相当于return Promise.resolve(11)
- 执行第二个
.then()
, 输出 "then2 11" , 没有返回值 , 相等于return Promise.resolve(undefined)
- 执行第三个
.then()
, 输出 "then3 undefined" , 抛出错误 , 相当于执行了return Promise.reject(new Error('then3 error'))
- 错误被
.catch()
捕获 , 输出 "catch Error: then3 error" , 并 return 'catch 返回值' , 相当于return Promise.resolve('catch 返回值')
- 最后的
.then()
会接收到返回值 , 输出 "then3 catch 返回值"
结果 :
then1 start
then2 11
then3 undefined
catch Error: then3 error
then3 catch返回值
总结 :
- return 任意值 ,【不包含
Promise.reject
和(throw new Error)
】, 会被包装成一个状态为fulfilled
的Promise
对象 ,相当于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);
});
分析 :
- Promise 新建后就会立即执行 , resolve(1) 使 Promise 状态变为 fulfilled
- 执行第一个
then
, return new Error 会被隐式转换为 Promise.resolve(new Error), 所以会进入下一个 then- 执行第二个
then
, 输出 "then2,error-2" ; throw new Error("error-2") 会中断 当前 then`的执行 , 创建一个 Promise.reject 对象- 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);
});
分析 :
- new Promise 中 reject() , Promise 状态由 pending 转为 rejected
- 执行第一个 catch() , 抛错会进 catch || 未抛错会进 then
- 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);
});
分析:
- finally 的回调函数接会立即执行 , 它不接收前一个 Promise 的结果 , 输出 "1"
- return "finally" 会被忽略 , 因为 then 会接收到前一个 Promise.resolve("start") 传递的参数 , 所以输出 "then start"
- 第二个 finally 抛出错误 , 被 .catch() 捕获 , 输出 "catch Error: error "。
结果:
1
then start
catch Error: error
总结:
- 无论
Promise
成功或失败 ,finally
的回调函数都会在当前Promise
完成后立即执行 , 而不是在链的末尾执行 ; finally
不接收前一个Promise
的结果 , 通常不需参数 ;- 无异常时 ,
finally
返回值不影响Promise
链 ; 若抛出错误 , 将由最近的catch
捕获 finally
专用于执行与结果无关的通用操作 , 不涉及值的传递 , 如果需要处理Promise
的结果 , 应该在then
或catch
中进行 ;
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);
分析 :
- 执行全局上下文 , 创建名为
p_1
的 Promise 对象 , 其状态为pending
, 输出 "1"- 调用 resolve() ,
p_1
的状态变为fulfilled
- 输出 "2"
- 当调用
p_1.then
并传入一个回调函数时,这个回调函数会被注册为一个微任务 ,Micro=[微任务1]
- 输出 "4"
- 全局上下文执行完毕 , 检查微任务队列 , 输出 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);
分析 :
- 执行全局上下文,创建名为
p_2
的 Promise 对象,状态为pending
,输出 "1"。- 将 setTimeout 中的回调函数加入宏任务队列 ,
Macro=[宏任务1]
。p_2.then
注册回调,进入等待状态。
p_2.then
中的回调函数被创建并注册,但它不会立即执行 ;- 要等
p_2
的状态变变为fulfilled
或rejected
时 , 才会进入微任务队列 ;
- 继续执行全局上下文,输出 "5"。
- 全局上下文执行结束,去检查微任务列表,由于
p_2
的状态尚未改变,微任务队列为空。- 从宏任务队列中取出第一个任务执行。此时执行 setTimeout 中的代码:
- 输出 "2"
- 调用 resolve(),使得
p_2
的状态变为fulfilled
。p_2.then
的回调被加入微任务队列,此时微任务队列为Micro=[微任务1]
。- 输出 "3"`。
- 清空微任务队列,输出 "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! ");
分析 :
- 执行全局上下文 , 输出 Before function!
- 调用 myFunc() , 输出 In Function!
- 遇到 await one()
- one() 返回一个已解析的 Promise , 状态为
fulfilled
, 值为 "One! "await
关键字使得 myFunc 函数内部的执行暂停 , (await 后面的代码会与 async 函数的剩余部分一起作为一个整体) 运行在微任务中!!!Micro=[微任务1]
- 输出 After function!
- 全局上下文执行结束 , 清空微任务队列 , 输出 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);
});
分析 :
- 创建 p1,状态变为 fulfilled,第一个 then 回调加入微任务队列,
Micro=[微任务1]
- 执行微任务 1
- 创建 p2 , 输出 "6"
- resolve(5) , p2 的第一个 then 回调加入微任务队列 ,
Micro=[微任务2]
- return 1 , p1 的第二个 then 回调 加入微任务队列 ,
Micro=[微任务2,微任务3]
- 执行微任务 2
- 输出 5
- p2 的第二个 then 回调 加入微任务队列 ,
Micro=[微任务3,微任务4]
- 执行微任务 3
- 输出 9
- p1 的第三个 then 回调 加入微任务队列 ,
Micro=[微任务4,微任务5]
- 执行微任务 4 , 输出 8
- 执行微任务 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);