第十九节:Promise详解(背景、用法、三种状态、对象方法、类方法)
一. Promise基础
1. 背景
在Promise出现之前,异步任务的处理方式,以发送请求为例,响应成功和失败返回不同的信息,这个时候我们需要自己封装回调方法。但这样有很大的弊端:
(1). 在自己封装的方法里,必须使用自己设计的callBack名称,不能写错名字
(2). 别人如果使用我们封装的方法,必须看文档或者源码,因为他不知道successCallBack到底是成功的回调还是失败的回调,否则不知道怎么拿到返回值哦.
代码分享-自己封装回调
function requesetData(url, successCallBack, failureCallBack) { // 模拟异步的网络请求 setTimeout(() => { // url传入ypf,响应成功,否则失败 if (url == "ypf") { successCallBack("请求成功"); } else { failureCallBack("请求失败"); } }, 2000); } // 调用 requesetData( "ypf", res => { console.log("res:" + res); }, err => { console.log("error:" + err); } );
2. 什么是Promise?
Promise是ES6新增的一个类,可以翻译为承诺(许诺、契约等),Promise的规范是固定,使用者可以按照该固定的规范进行回调的获取。
当我们new创建1个Promise对象时候,需要传入一个回调函数(称之为executor)
(1).这个回调函数会被立即执行,并且给传入另外两个回调函数resolve、reject;
(2).当我们调用resolve回调函数时,表示回调成功,会执行Promise对象的then方法中的【第一个参数位置传入】的回调函数;
(3).当我们调用reject回调函数时,表示回调失败,会执行Promise对象的then方法中的【第二个参数位置传入】的回调函数,或者catch方法传入的回调函数;
代码分享-promise基本用法
{ console.log("--------2.1 Promise基本用法-----------"); const resultPromise1 = new Promise((resolve, reject) => { resolve("成功了"); }); resultPromise1.then(res => console.log(res)); const resultPromise2 = new Promise((resolve, reject) => { reject("失败了"); }); // 失败回调获取-写法1 resultPromise2.then(null, err => console.log(err)); // 失败回调获取-写法2 resultPromise2.then(null, null).catch(err => console.log(err)); }
案例:改造1中的异步请求的封装
{ console.log("--------2.2 Promise用于异步请求函数封装-----------"); function requesetData(data) { return new Promise((resolve, reject) => { // 模拟异步的网络请求 setTimeout(() => { if (data == "ypf") { resolve("获取成功111111"); } else { reject("获取失败22222"); } }, 2000); }); } // 调用-写法1 const resultPromise = requesetData("ypf"); resultPromise.then( res => { console.log(res); }, err => { console.log(err); } ); // 调用-写法2 const resultPromise2 = requesetData("lmr"); resultPromise2 .then(res => { console.log(res); }) .catch(err => { console.log(err); }); }
3. Promise的三种状态
(1). 待定(pending): 初始状态,既没有被兑现,也没有被拒绝;当执行executor中的代码时,处于该状态;
(2). 已兑现(fulfilled): 意味着操作成功完成;执行了resolve时,处于该状态; (这个状态通常也叫做 resolved,更容易被记住)
(3). 已拒绝(rejected): 意味着操作失败;执行了reject时,处于该状态;
注:一旦状态被确定下来,Promise的状态会被 锁死,该Promise的状态是不可更改的。
在我们调用resolve的时候,如果resolve传入的值本身不是一个Promise,那么会将该Promise的状态变成 兑现(fulfilled); 在之后我们去调用reject时,已经不会有任何的响应了(并不是这行代码不会执行,而是无法改变Promise状态)
代码分享:
{ console.log("----------3. Promise的三种状态----------"); /* const myPromise = new Promise((resolve, reject) => { resolve("333"); }); myPromise.then(res => {}).catch(err => {}); */ // 下面代码是立即执行的,等价于上面代码 new Promise((resolve, reject) => { // 状态1 该进来的时候是 pending状态, 待定状态 console.log("------------------------"); resolve(11); //状态2 fulfilled 成功状态 reject(22); //状态3 rejected 失败状态 (注:不生效了,因为前面resolve已经确定状态了,就锁定了,不能再改了) }) .then(res => { console.log(res); }) .catch(err => { console.log(err); }); }
4. resolve参数【重点】
(1). 如果resolve传入一个普通的值或者对象,那么这个值会作为then回调的参数。
(2). 如果resolve中传入的是另外一个Promise,那么这个新Promise会决定原外层Promise的状态.
A. 内层Promise调用resolve(fulfilled状态),那么就进入then中的回调成功的位置
B. 内层Promise调用reject(rejected状态),那么就进入then中回调失败的位置 或者 catch 中
(3). resolve中传入的是一个对象,并且这个对象有实现then方法,那么会执行该then方法,并且根据then方法的结果来决定Promise的状态
A. then中调用resolve,那么最终进入then中的回调成功的位置
B. then中调用reject,那么最终就进入then中回调失败的位置 或者 catch 中
代码分享:
// 4.1 传入普通值 { console.log("--------------4.1 传入普通值------------------"); const promiseResult = new Promise((resolve, reject) => { resolve("ypf1"); }); promiseResult.then(res => console.log(res)); //ypf1 } // 4.2 传入Promise { console.log("-------------- 4.2 传入Promise------------------"); const promiseResult = new Promise((resolve, reject) => { resolve( new Promise((resolve2, reject2) => { reject2("ypf2"); }) ); }); promiseResult.then(null, err => console.log(err)); //ypf2 } // 4.3 传入对象,且对象有实现then方法 { console.log("------4.3 传入对象,且对象有实现then方法-----------"); const promiseResult = new Promise((resolve, reject) => { resolve({ then: function (resolve2, reject2) { reject2("ypf3"); }, }); }); promiseResult.then(null, err => console.log(err)); //ypf3 }
二. Promise对象方法
1. then方法
(1). then方法接收两个参数:
A.fulfilled的回调函数:当状态变成fulfilled时会回调的函数;
B.reject的回调函数:当状态变成reject时会回调的函数;
注:reject的回调函数等价于catch方法中调用
// 1.1 then的两个参数 { console.log("---------- 1.1 then的两个参数--------------"); const promiseResult = new Promise((resolve, reject) => { reject("出错了"); }); // promiseResult.then(null, err => console.log(err)); // 等价于 promiseResult.catch(err => console.log(err)); }
(2).一个Promise的then方法是可以被多次调用的
代码分享:
{ console.log("--------1.2 then方法是可以被多次调用的-----------"); const promiseResult = new Promise((resolve, reject) => { resolve("成功了"); }); // 下面代码都输出 promiseResult.then(res => console.log(`res1:${res}`)); promiseResult.then(res => console.log(`res2:${res}`)); promiseResult.then(res => console.log(`res3:${res}`)); }
(3).返回值问题【重点】
then方法本身也是有返回值的, 它的返回值是Promise.
A. 如果我们返回的是一个普通值(数值/字符串/普通对象/undefined), 那么这个普通的值被作为一个新的Promise的resolve值
B. 如果我们返回的是一个Promise,那么当前的Promise的状态会由传入的Promise的状态来决定 相当于状态进行了移交 【原理同 ‘resolve参数’】
C. 如果返回的是一个对象, 并且该对象实现了then方法,则由then方法中的状态进行决定哦 【原理同 ‘resolve参数’】
D. 当then方法抛出一个异常时,那么它处于reject状态; 【详解下面catch方法的测试】
代码分享:
// 1.3.1 返回值是普通值 { console.log("-----1.3.1 返回值是普通值--------"); const promiseResult = new Promise((resolve, reject) => { resolve("hahaha"); }); promiseResult .then(res => { return "ypf1"; //不写返回值,返回的是undefined }) .then(res => console.log(res)); //输出ypf1 } // 1.3.2 返回值是Promise { console.log("-----1.3.2 返回值是Promise--------"); const promiseResult = new Promise((resolve, reject) => { resolve("hahaha"); }); promiseResult .then(res => { return new Promise((resolve, reject) => { reject("出错了"); //进行了状态移交,状态变为rejected,所以在后面then的第二个参数中接受 }); }) .then(null, err => console.log(err)); //输出ypf1 } // 1.3.3 返回值是对象,里面实现了then方法 { console.log("-----1.3.3 返回值是对象,里面实现了then方法-------"); const promiseResult = new Promise((resolve, reject) => { resolve("hahaha"); }); promiseResult .then(res => { return { then: function (resolve, reject) { reject("出错了"); }, }; }) .then(null, err => console.log(err)); //输出ypf1 }
2. catch方法
(1). 当then方法抛出一个异常时,那么它处于rejected状态 →→ 就会进入后面的catch 【原理 见上述返回值问题】
// 2.1. then中抛出异常,直接进入catch { console.log("--------2.1. then中抛出异常,直接进入catch---------------"); const promise = new Promise((resolve, reject) => { throw new Error("rejected status"); }); promise.catch(err => { console.log("err:", err); }); }
(2). catch的多次调用:一个Promise的catch方法是可以被多次调用的
// 2.2 catch的多次调用 { console.log("------- 2.2 catch的多次调用-------------"); const promise = new Promise((resolve, reject) => { reject("失败了"); }); // 下面的代码都会被执行 promise.catch(err => console.log("err1:", err)); promise.catch(err => console.log("err2:", err)); promise.catch(err => console.log("err3:", err)); }
(3). catch的返回值:catch方法也是会返回一个Promise对象的,所以catch方法后面我们可以继续调用then方法或者catch方法
A. 返回普通值(数值/字符串/普通对象/undefined),进入下一个then
B. 抛出异常,进入下一个catch
代码分享:
{ console.log("-------2.3 catch的返回值------------"); const promise = new Promise((resolve, reject) => { reject("ypf1"); }); promise .then(res => { console.log("res:", res); }) .catch(err => { console.log("err:", err); //先执行这句话 【err: ypf1】 return "ypf2"; }) .then(res => { console.log("res result:", res); //再执行这句话 【res result: ypf2】 }) .catch(err => { console.log("err result:", err); }); }
3. finally方法
(1).finally是在ES9(ES2018)中新增的一个特性:表示无论Promise对象无论变成fulfilled还是reject状态,最终都会被执行的代码。
(2).finally方法是不接收参数的,因为无论前面是fulfilled状态,还是reject状态,它都会执行。
代码分享:
{ console.log("3.-----------------3. finally方法--------------------------"); const promise = new Promise((resolve, reject) => { resolve("resolve message"); // reject("reject message"); }); promise .then(res => { console.log("res:", res); }) .catch(err => { console.log("err:", err); }) .finally(() => { console.log("finally code execute"); }); }
三. Promise类方法
1. resolve方法
(1). Promise.resolve的用法相当于new Promise,并且执行resolve操作.
(2). resolve方法参数问题:
A:参数是一个普通的值或者对象
B:参数本身是Promise
C:参数是一个thenable
PS:这里和前面讲的resolve参数完全一样哦。【详见前面 4.resolve参数】
{ console.log("---------------1. resolve方法-------------------"); const promiseResult = Promise.resolve("ok"); // 等价于上面的话 const promiseResult2 = new Promise((resolve, reject) => { resolve("ok"); }); // 这里演示了 Promise.resolve("ok");传字符串的情况,至于传入promise和含then方法的对象,不再演示了 promiseResult.then(res => console.log(res)); }
2. reject方法
(1). Promise.reject的用法相当于new Promise,只是会调用reject操作
特别注意:Promise.reject传入的参数无论是什么形态,都会直接作为reject状态的参数传递到catch的
代码分享:
{ console.log("--------------2. reject方法-------------------"); const promiseResult = Promise.reject("error1了"); // 等价于上面的话 // const promiseResult2 = new Promise((resolve, reject) => { // reject("error2了"); // }); promiseResult.catch(err => console.log(err)); } // 注意: 无论传入什么值都是一样的(这里不分情况,都是进入catch) { console.log("--- 注意: 无论传入什么值这里不分情况,都是进入catch"); const promiseResult = Promise.reject( new Promise((resolve, reject) => { resolve("okokok"); }) ); // 输出:Promise { 'okokok' } promiseResult.catch(err => console.log(err)); }
3. all方法
(1). 它的作用是将多个Promise包裹在一起形成一个新的Promise;
(2). 新的Promise状态由包裹的所有Promise共同决定:
A. 当所有的Promise状态变成fulfilled状态时(resolved),新的Promise状态为fulfilled,并且会将所有Promise的返回值组成一个数组;
B. 当有一个Promise状态为reject时,新的Promise状态为reject,并且会将第一个reject的返回值作为参数;
代码分享:
{ console.log("------------- 3. all方法-------------------"); const p1 = new Promise((resolve, reject) => { setTimeout(() => resolve(11111), 1000); }); const p2 = new Promise((resolve, reject) => { setTimeout(() => resolve(22222), 2000); }); const p3 = new Promise((resolve, reject) => { setTimeout(() => reject(3333), 3000); }); // 测试1-全部promise都变为fulfilled Promise.all([p2, p1, "test1"]) .then(res => console.log("res:" + res)) //res:22222,11111,test1 (这里是按照传入的顺序进行返回的) .catch(err => console.log("err:" + err)); // 测试2-其中1个变为rejected Promise.all([p2, p1, p3]) .then(res => console.log("res:" + res)) .catch(err => console.log("err:" + err)); //err:3333 }
4. allSettled方法
(1). 背景
all方法有一个缺陷:当有其中一个Promise变成reject状态时,新Promise就会立即变成对应的reject状态。
那么对于resolved的,以及依然处于pending状态的Promise,我们是获取不到对应的结果的;
(2). 在ES11(ES2020)中,添加了新的API Promise.allSettled:该方法会在所有的Promise都有结果(settled),无论是fulfilled,还是reject时,才会有最终的状态;
并且这个Promise的结果一定是fulfilled的;
(3).返回值:
allSettled的结果是一个数组,数组中存放着每一个Promise的结果,并且是对应一个对象的;这个对象中包含status状态,以及对应的value值;
console.log(item.status, item.value, item.reason);
A.statue:表示状态, fulfilled 或 rejected
B.value: fulfilled时对应的内容
C.reason: reject时对应的内容
代码分享:
{ console.log("------------- 4. allSettled方法-------------------"); const p1 = new Promise((resolve, reject) => { setTimeout(() => resolve(11111), 1000); }); const p2 = new Promise((resolve, reject) => { setTimeout(() => resolve(22222), 2000); }); const p3 = new Promise((resolve, reject) => { setTimeout(() => reject(3333), 3000); }); // 测试1-全部promise都有结果 Promise.allSettled([p1, p2, p3]) .then(res => { for (const item of res) { console.log(item.status, item.value, item.reason); } }) .catch(err => console.log("err:" + err)); }
运行结果:
5. race方法
如果有一个Promise有了结果,我们就希望决定最终新Promise的状态,那么可以使用race方法:
race是竞技、竞赛的意思,表示多个Promise相互竞争,谁先有结果,那么就使用谁的结果;
代码分享:
{ console.log("------------- 5. race方法-------------------"); const p1 = new Promise((resolve, reject) => { setTimeout(() => resolve(11111), 1000); }); const p2 = new Promise((resolve, reject) => { setTimeout(() => resolve(22222), 2000); }); const p3 = new Promise((resolve, reject) => { setTimeout(() => reject(3333), 3000); }); Promise.race([p1, p2, p3]) .then(res => { console.log("res:", res); //上面p1先执行完,所以状态是fulfilled,输出结果为:11111 }) .catch(err => { console.log("err:", err); }); }
6. any方法
any方法是ES12中新增的方法,和race方法是类似的:
(1). any方法会等到一个fulfilled状态,才会决定新Promise的状态;
(2). 如果所有的Promise都是reject的,那么也会等到所有的Promise都变成rejected状态;
注: 如果所有的Promise都是reject的,那么会报一个AggregateError的错误。
代码分享:
{ console.log("------------- 5. race方法-------------------"); const p1 = new Promise((resolve, reject) => { setTimeout(() => resolve(11111), 1000); }); const p2 = new Promise((resolve, reject) => { setTimeout(() => resolve(22222), 2000); }); const p3 = new Promise((resolve, reject) => { setTimeout(() => reject(3333), 3000); }); Promise.any([p1, p2, p3]) .then(res => { console.log("res:", res); //上面p1先执行完,所以状态是fulfilled }) .catch(err => { console.log("err:", err); }); }
!
- 作 者 : Yaopengfei(姚鹏飞)
- 博客地址 : http://www.cnblogs.com/yaopengfei/
- 声 明1 : 如有错误,欢迎讨论,请勿谩骂^_^。
- 声 明2 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。