第十九節: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 : 原創博客請在轉載時保留原文鏈接或在文章開頭加上本人博客地址,否則保留追究法律責任的權利。
 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM