第十九节: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);
        });
}
View Code

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 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。
 

 

posted @ 2022-04-01 17:46  Yaopengfei  阅读(1867)  评论(1编辑  收藏  举报