Promise入門(mén)詳解和基本用法

異步調(diào)用

異步

JavaScript的執(zhí)行環(huán)境是單線(xiàn)程。

所謂單線(xiàn)程,是指JS引擎中負(fù)責(zé)解釋和執(zhí)行JavaScript代碼的線(xiàn)程只有一個(gè),也就是一次只能完成一項(xiàng)任務(wù),這個(gè)任務(wù)執(zhí)行完后才能執(zhí)行下一個(gè),它會(huì)「阻塞」其他任務(wù)。這個(gè)任務(wù)可稱(chēng)為主線(xiàn)程。

異步模式可以一起執(zhí)行多個(gè)任務(wù)。

常見(jiàn)的異步模式有以下幾種:

  • 定時(shí)器

  • 接口調(diào)用

  • 事件函數(shù)
    今天這篇文章,我們重點(diǎn)講一下接口調(diào)用。接口調(diào)用里,重點(diǎn)講一下Promise。

接口調(diào)用的方式

js 中常見(jiàn)的接口調(diào)用方式,有以下幾種:

  • 原生ajax
  • 基于jQuery的ajax
  • Fetch
  • Promise
  • axios

多次異步調(diào)用的依賴(lài)分析

  • 多次異步調(diào)用的結(jié)果,順序可能不同步。

  • 異步調(diào)用的結(jié)果如果存在依賴(lài),則需要嵌套。

在ES5中,當(dāng)進(jìn)行多層嵌套回調(diào)時(shí),會(huì)導(dǎo)致代碼層次過(guò)多,很難進(jìn)行維護(hù)和二次開(kāi)發(fā);而且會(huì)導(dǎo)致回調(diào)地獄的問(wèn)題。ES6中的Promise 就可以解決這兩個(gè)問(wèn)題。

Promise 概述

Promise的介紹和優(yōu)點(diǎn)

ES6中的Promise 是異步編程的一種方案。從語(yǔ)法上講,Promise 是一個(gè)對(duì)象,它可以獲取異步操作的消息。

Promise對(duì)象, 可以將異步操作以同步的流程表達(dá)出來(lái)。使用 Promise 主要有以下好處:

  • 可以很好地解決回調(diào)地獄的問(wèn)題(避免了層層嵌套的回調(diào)函數(shù))。

  • 語(yǔ)法非常簡(jiǎn)潔。Promise 對(duì)象提供了簡(jiǎn)潔的API,使得控制異步操作更加容易。

回調(diào)地獄的舉例

假設(shè)買(mǎi)菜、做飯、洗碗都是異步的。

但真實(shí)的場(chǎng)景中,實(shí)際的操作流程是:買(mǎi)菜成功之后,才能開(kāi)始做飯。做飯成功后,才能開(kāi)始洗碗。這里面就涉及到了多層嵌套調(diào)用,也就是回調(diào)地獄。

Promise 的基本用法

(1)使用new實(shí)例化一個(gè)Promise對(duì)象,Promise的構(gòu)造函數(shù)中傳遞一個(gè)參數(shù)。這個(gè)參數(shù)是一個(gè)函數(shù),該函數(shù)用于處理異步任務(wù)。

(2)并且傳入兩個(gè)參數(shù):resolve和reject,分別表示異步執(zhí)行成功后的回調(diào)函數(shù)和異步執(zhí)行失敗后的回調(diào)函數(shù);

(3)通過(guò) promise.then() 處理返回結(jié)果。這里的 p 指的是 Promise實(shí)例。
代碼舉例如下:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Document</title>
    </head>
    <body>
        <script>
            // 第一步:model層的接口封裝
            const promise = new Promise((resolve, reject) => {
                // 這里做異步任務(wù)(比如ajax 請(qǐng)求接口。這里暫時(shí)用定時(shí)器代替)
                setTimeout(function() {
                    var data = { retCode: 0, msg: 'qianguyihao' }; // 接口返回的數(shù)據(jù)
                    if (data.retCode == 0) {
                        // 接口請(qǐng)求成功時(shí)調(diào)用
                        resolve(data);
                    } else {
                        // 接口請(qǐng)求失敗時(shí)調(diào)用
                        reject({ retCode: -1, msg: 'network error' });
                    }
                }, 100);
            });

            // 第二步:業(yè)務(wù)層的接口調(diào)用。這里的 data 就是 從 resolve 和 reject 傳過(guò)來(lái)的,也就是從接口拿到的數(shù)據(jù)
            promise.then(data => {
                // 從 resolve 獲取正常結(jié)果
                console.log(data);
            }).catch(data => {
                // 從 reject 獲取異常結(jié)果
                console.log(data);
            });
        </script>
    </body>
</html>

上方代碼中,當(dāng)從接口返回的數(shù)據(jù)data.retCode的值不同時(shí),可能會(huì)走resolve,也可能會(huì)走 reject,這個(gè)由你自己的業(yè)務(wù)決定。

promise對(duì)象的3個(gè)狀態(tài)(了解即可)

  • 初始化狀態(tài)(等待狀態(tài)):pending

  • 成功狀態(tài):fullfilled

  • 失敗狀態(tài):rejected
    (1)當(dāng)new Promise()執(zhí)行之后,promise對(duì)象的狀態(tài)會(huì)被初始化為pending,這個(gè)狀態(tài)是初始化狀態(tài)。new Promise()這行代碼,括號(hào)里的內(nèi)容是同步執(zhí)行的。括號(hào)里定義一個(gè)function,function有兩個(gè)參數(shù):resolve和reject。如下:

  • 如果請(qǐng)求成功了,則執(zhí)行resolve(),此時(shí),promise的狀態(tài)會(huì)被自動(dòng)修改為fullfilled。

  • 如果請(qǐng)求失敗了,則執(zhí)行reject(),此時(shí),promise的狀態(tài)會(huì)被自動(dòng)修改為rejected

(2)promise.then()方法,括號(hào)里面有兩個(gè)參數(shù),分別代表兩個(gè)函數(shù) function1 和 function2:

  • 如果promise的狀態(tài)為fullfilled(意思是:如果請(qǐng)求成功),則執(zhí)行function1里的內(nèi)容

  • 如果promise的狀態(tài)為rejected(意思是,如果請(qǐng)求失敗),則執(zhí)行function2里的內(nèi)容

另外,resolve()和reject()這兩個(gè)方法,是可以給promise.then()傳遞參數(shù)的。

完整代碼舉例如下:

let promise = new Promise((resolve, reject) => {
        //進(jìn)來(lái)之后,狀態(tài)為pending
        console.log('111');  //這行代碼是同步的
        //開(kāi)始執(zhí)行異步操作(這里開(kāi)始,寫(xiě)異步的代碼,比如ajax請(qǐng)求 or 開(kāi)啟定時(shí)器)
        if (異步的ajax請(qǐng)求成功) {
            console.log('333');
            resolve('haha');//如果請(qǐng)求成功了,請(qǐng)寫(xiě)resolve(),此時(shí),promise的狀態(tài)會(huì)被自動(dòng)修改為fullfilled
        } else {
            reject('555');//如果請(qǐng)求失敗了,請(qǐng)寫(xiě)reject(),此時(shí),promise的狀態(tài)會(huì)被自動(dòng)修改為rejected
        }
    })
    console.log('222');

    //調(diào)用promise的then()
    promise.then((successMsg) => {
            //如果promise的狀態(tài)為fullfilled,則執(zhí)行這里的代碼
            console.log(successMsg, '成功了');
        }
        , (errorMsg) => {
            //如果promise的狀態(tài)為rejected,則執(zhí)行這里的代碼
            console.log(errorMsg, '失敗了');

        }
    )

基于 Promise 處理 多次 Ajax 請(qǐng)求(鏈?zhǔn)秸{(diào)用)【重要】

實(shí)際開(kāi)發(fā)中,我們經(jīng)常需要同時(shí)請(qǐng)求多個(gè)接口。比如說(shuō):在請(qǐng)求完接口1的數(shù)據(jù)data1之后,需要根據(jù)data1的數(shù)據(jù),繼續(xù)請(qǐng)求接口2,獲取data2;然后根據(jù)data2的數(shù)據(jù),繼續(xù)請(qǐng)求接口3。

這種場(chǎng)景其實(shí)就是接口的多層嵌套調(diào)用。有了 promise之后,我們可以把多層嵌套調(diào)用按照線(xiàn)性的方式進(jìn)行書(shū)寫(xiě),非常優(yōu)雅。

也就是說(shuō):Promise 可以把原本的多層嵌套調(diào)用改進(jìn)為鏈?zhǔn)秸{(diào)用

代碼舉例:(多次 Ajax請(qǐng)求,鏈?zhǔn)秸{(diào)用)

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Document</title>
    </head>
    <body>
        <script type="text/javascript">
            /*
              基于Promise發(fā)送Ajax請(qǐng)求
            */
            function queryData(url) {
                var promise = new Promise((resolve, reject) => {
                    var xhr = new XMLHttpRequest();
                    xhr.onreadystatechange = function() {
                        if (xhr.readyState != 4) return;
                        if (xhr.readyState == 4 && xhr.status == 200) {
                            // 處理正常情況
                            resolve(xhr.responseText); // xhr.responseText 是從接口拿到的數(shù)據(jù)
                        } else {
                            // 處理異常情況
                            reject('接口請(qǐng)求失敗');
                        }
                    };
                    xhr.responseType = 'json'; // 設(shè)置返回的數(shù)據(jù)類(lèi)型
                    xhr.open('get', url);
                    xhr.send(null); // 請(qǐng)求接口
                });
                return promise;
            }
            // 發(fā)送多個(gè)ajax請(qǐng)求并且保證順序
            queryData('http://localhost:3000/api1')
                .then(
                    data1 => {
                        console.log(JSON.stringify(data1));
                        // 請(qǐng)求完接口1后,繼續(xù)請(qǐng)求接口2
                        return queryData('http://localhost:3000/api2');
                    },
                    error1 => {
                        console.log(error1);
                    }
                )
                .then(
                    data2 => {
                        console.log(JSON.stringify(data2));
                        // 請(qǐng)求完接口2后,繼續(xù)請(qǐng)求接口3
                        return queryData('http://localhost:3000/api3');
                    },
                    error2 => {
                        console.log(error2);
                    }
                )
                .then(
                    data3 => {
                        // 獲取接口3返回的數(shù)據(jù)
                        console.log(JSON.stringify(data3));
                    },
                    error3 => {
                        console.log(error3);
                    }
                );
        </script>
    </body>
</html>

return 的函數(shù)返回值

return 后面的返回值,有兩種情況:

情況1:返回 Promise 實(shí)例對(duì)象。返回的該實(shí)例對(duì)象會(huì)調(diào)用下一個(gè) then。

情況2:返回普通值。返回的普通值會(huì)直接傳遞給下一個(gè)then,通過(guò) then 參數(shù)中函數(shù)的參數(shù)接收該值。

我們針對(duì)上面這兩種情況,詳細(xì)解釋一下。

情況1:返回 Promise 實(shí)例對(duì)象

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Document</title>
    </head>
    <body>
        <script type="text/javascript">
            /*
              基于Promise發(fā)送Ajax請(qǐng)求
            */
            function queryData(url) {
                return new Promise((resolve, reject) => {
                    var xhr = new XMLHttpRequest();
                    xhr.onreadystatechange = function() {
                        if (xhr.readyState != 4) return;
                        if (xhr.readyState == 4 && xhr.status == 200) {
                            // 處理正常情況
                            resolve(xhr.responseText);
                        } else {
                            // 處理異常情況
                            reject('接口請(qǐng)求失敗');
                        }
                    };
                    xhr.responseType = 'json'; // 設(shè)置返回的數(shù)據(jù)類(lèi)型
                    xhr.open('get', url);
                    xhr.send(null); // 請(qǐng)求接口
                });
            }
            // 發(fā)送多個(gè)ajax請(qǐng)求并且保證順序
            queryData('http://localhost:3000/api1')
                .then(
                    data1 => {
                        console.log(JSON.stringify(data1));
                        return queryData('http://localhost:3000/api2');
                    },
                    error1 => {
                        console.log(error1);
                    }
                )
                .then(
                    data2 => {
                        console.log(JSON.stringify(data2));
                        // 這里的 return,返回的是 Promise 實(shí)例對(duì)象
                        return new Promise((resolve, reject) => {
                            resolve('qianguyihao');
                        });
                    },
                    error2 => {
                        console.log(error2);
                    }
                )
                .then(data3 => {
                    console.log(data3);
                });
        </script>
    </body>
</html>

情況2:返回 普通值

<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Document</title>
    </head>
    <body>
        <script type="text/javascript">
            /*
              基于Promise發(fā)送Ajax請(qǐng)求
            */
            function queryData(url) {
                return new Promise((resolve, reject) => {
                    var xhr = new XMLHttpRequest();
                    xhr.onreadystatechange = function() {
                        if (xhr.readyState != 4) return;
                        if (xhr.readyState == 4 && xhr.status == 200) {
                            // 處理正常情況
                            resolve(xhr.responseText);
                        } else {
                            // 處理異常情況
                            reject('接口請(qǐng)求失敗');
                        }
                    };
                    xhr.responseType = 'json'; // 設(shè)置返回的數(shù)據(jù)類(lèi)型
                    xhr.open('get', url);
                    xhr.send(null); // 請(qǐng)求接口
                });
            }
            // 發(fā)送多個(gè)ajax請(qǐng)求并且保證順序
            queryData('http://localhost:3000/api1')
                .then(
                    data1 => {
                        console.log(JSON.stringify(data1));
                        return queryData('http://localhost:3000/api2');
                    },
                    error1 => {
                        console.log(error1);
                    }
                )
                .then(
                    data2 => {
                        console.log(JSON.stringify(data2));
                        // 返回普通值
                        return 'qianguyihao';
                    },
                    error2 => {
                        console.log(error2);
                    }
                )
                /*
                    既然上方返回的是 普通值,那么,這里的 then 是誰(shuí)來(lái)調(diào)用呢?
                    答案是:這里會(huì)產(chǎn)生一個(gè)新的 默認(rèn)的 promise實(shí)例,來(lái)調(diào)用這里的then,確??梢岳^續(xù)進(jìn)行鏈?zhǔn)讲僮鳌?                */
                .then(data3 => {
                    // 這里的 data3 接收的是 普通值 'qianguyihao'
                    console.log(data3);
                });
        </script>
    </body>
</html>

Promise 的常用API:實(shí)例方法【重要】

Promise 自帶的API提供了如下實(shí)例方法:

  • promise.then():獲取異步任務(wù)的正常結(jié)果。

  • promise.catch():獲取異步任務(wù)的異常結(jié)果。

  • promise.finaly():異步任務(wù)無(wú)論成功與否,都會(huì)執(zhí)行。

代碼舉例如下。

寫(xiě)法1:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Document</title>
    </head>
    <body>
        <script>
            function queryData() {
                return new Promise((resolve, reject) => {
                    setTimeout(function() {
                        var data = { retCode: 0, msg: 'qianguyihao' }; // 接口返回的數(shù)據(jù)
                        if (data.retCode == 0) {
                            // 接口請(qǐng)求成功時(shí)調(diào)用
                            resolve(data);
                        } else {
                            // 接口請(qǐng)求失敗時(shí)調(diào)用
                            reject({ retCode: -1, msg: 'network error' });
                        }
                    }, 100);
                });
            }

            queryData()
                .then(data => {
                    // 從 resolve 獲取正常結(jié)果
                    console.log('接口請(qǐng)求成功時(shí),走這里');
                    console.log(data);
                })
                .catch(data => {
                    // 從 reject 獲取異常結(jié)果
                    console.log('接口請(qǐng)求失敗時(shí),走這里');
                    console.log(data);
                })
                .finally(() => {
                    console.log('無(wú)論接口請(qǐng)求成功與否,都會(huì)走這里');
                });
        </script>
    </body>
</html>
寫(xiě)法2:(和上面的寫(xiě)法1等價(jià))
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Document</title>
    </head>
    <body>
        <script>
            function queryData() {
                return new Promise((resolve, reject) => {
                    setTimeout(function() {
                        var data = { retCode: 0, msg: 'qianguyihao' }; // 接口返回的數(shù)據(jù)
                        if (data.retCode == 0) {
                            // 接口請(qǐng)求成功時(shí)調(diào)用
                            resolve(data);
                        } else {
                            // 接口請(qǐng)求失敗時(shí)調(diào)用
                            reject({ retCode: -1, msg: 'network error' });
                        }
                    }, 100);
                });
            }

            queryData()
                .then(
                    data => {
                        // 從 resolve 獲取正常結(jié)果
                        console.log('接口請(qǐng)求成功時(shí),走這里');
                        console.log(data);
                    },
                    data => {
                        // 從 reject 獲取異常結(jié)果
                        console.log('接口請(qǐng)求失敗時(shí),走這里');
                        console.log(data);
                    }
                )
                .finally(() => {
                    console.log('無(wú)論接口請(qǐng)求成功與否,都會(huì)走這里');
                });
        </script>
    </body>
</html>

注意:寫(xiě)法1和寫(xiě)法2的作用是完全等價(jià)的。只不過(guò),寫(xiě)法2是把 catch 里面的代碼作為 then里面的第二個(gè)參數(shù)而已。

Promise 的常用API:對(duì)象方法【重要】

Promise 自帶的API提供了如下對(duì)象方法:

  • Promise.all():并發(fā)處理多個(gè)異步任務(wù),所有任務(wù)都執(zhí)行成功,才能得到結(jié)果。

  • Promise.race(): 并發(fā)處理多個(gè)異步任務(wù),只要有一個(gè)任務(wù)執(zhí)行成功,就能得到結(jié)果。

下面來(lái)詳細(xì)介紹。
Promise.all() 代碼舉例
代碼舉例:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Document</title>
    </head>
    <body>
        <script type="text/javascript">
            /*
              封裝 Promise 接口調(diào)用
            */
            function queryData(url) {
                return new Promise((resolve, reject) => {
                    var xhr = new XMLHttpRequest();
                    xhr.onreadystatechange = function() {
                        if (xhr.readyState != 4) return;
                        if (xhr.readyState == 4 && xhr.status == 200) {
                            // 處理正常結(jié)果
                            resolve(xhr.responseText);
                        } else {
                            // 處理異常結(jié)果
                            reject('服務(wù)器錯(cuò)誤');
                        }
                    };
                    xhr.open('get', url);
                    xhr.send(null);
                });
            }

            var promise1 = queryData('http://localhost:3000/a1');
            var promise2 = queryData('http://localhost:3000/a2');
            var promise3 = queryData('http://localhost:3000/a3');

            Promise.all([promise1, promise2, promise3]).then(result => {
                console.log(result);
            });
        </script>
    </body>
</html>

Promise.race() 代碼舉例
代碼舉例:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Document</title>
    </head>
    <body>
        <script type="text/javascript">
            /*
              封裝 Promise 接口調(diào)用
            */
            function queryData(url) {
                return new Promise((resolve, reject) => {
                    var xhr = new XMLHttpRequest();
                    xhr.onreadystatechange = function() {
                        if (xhr.readyState != 4) return;
                        if (xhr.readyState == 4 && xhr.status == 200) {
                            // 處理正常結(jié)果
                            resolve(xhr.responseText);
                        } else {
                            // 處理異常結(jié)果
                            reject('服務(wù)器錯(cuò)誤');
                        }
                    };
                    xhr.open('get', url);
                    xhr.send(null);
                });
            }

            var promise1 = queryData('http://localhost:3000/a1');
            var promise2 = queryData('http://localhost:3000/a2');
            var promise3 = queryData('http://localhost:3000/a3');

            Promise.race([promise1, promise2, promise3]).then(result => {
                console.log(result);
            });
        </script>
    </body>
</html>
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 一、Promise 的含義 ??Promise 是異步編程的一種解決方案,比傳統(tǒng)的解決方案 回調(diào)函數(shù)和事件更合理和...
    懿左左閱讀 2,925評(píng)論 0 12
  • 一、promise的含義 Promise對(duì)象有以下兩個(gè)特點(diǎn)。 (1)對(duì)象的狀態(tài)不受外界影響。Promise對(duì)象代表...
    付出的前端路閱讀 1,037評(píng)論 0 0
  • 雖然很多移動(dòng)應(yīng)用是完全自容的(計(jì)算器,音板,待辦列表,照片應(yīng)用,手電筒應(yīng)用),但很多應(yīng)用靠著從外部源拉取數(shù)據(jù)。Fa...
    老牛啃碼閱讀 1,025評(píng)論 0 2
  • Promise是javascript中異步編程的一種解決方案,和傳統(tǒng)的異步編程方案(回調(diào)、事件)相比,使用更加簡(jiǎn)潔...
    boyrt閱讀 6,272評(píng)論 1 2
  • Promise是一個(gè)構(gòu)造函數(shù),它接收一個(gè)參數(shù),是函數(shù),并且傳入兩個(gè)參數(shù):resolve,reject,分別表示異步...
    擱淺_8633閱讀 1,056評(píng)論 0 2

友情鏈接更多精彩內(nèi)容