Promise入門詳解和基本用法

来源:https://www.cnblogs.com/qianguyihao/archive/2020/04/08/12660393.html
-Advertisement-
Play Games

非同步調用 非同步 JavaScript的執行環境是 單線程 。 所謂單線程,是指JS引擎中負責解釋和執行JavaScript代碼的線程只有一個,也就是一次只能完成一項任務,這個任務執行完後才能執行下一個,它會「阻塞」其他任務。這個任務可稱為主線程。 非同步模式可以一起執行 多個任務 。 常見的非同步模式有 ...


非同步調用

非同步

JavaScript的執行環境是單線程

所謂單線程,是指JS引擎中負責解釋和執行JavaScript代碼的線程只有一個,也就是一次只能完成一項任務,這個任務執行完後才能執行下一個,它會「阻塞」其他任務。這個任務可稱為主線程。

非同步模式可以一起執行多個任務

常見的非同步模式有以下幾種:

  • 定時器

  • 介面調用

  • 事件函數

今天這篇文章,我們重點講一下介面調用。介面調用里,重點講一下Promise

介面調用的方式

js 中常見的介面調用方式,有以下幾種:

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

多次非同步調用的依賴分析

  • 多次非同步調用的結果,順序可能不同步。

  • 非同步調用的結果如果存在依賴,則需要嵌套。

在ES5中,當進行多層嵌套回調時,會導致代碼層次過多,很難進行維護和二次開發;而且會導致回調地獄的問題。ES6中的Promise 就可以解決這兩個問題。

Promise 概述

Promise的介紹和優點

ES6中的Promise 是非同步編程的一種方案。從語法上講,Promise 是一個對象,它可以獲取非同步操作的消息。

Promise對象, 可以將非同步操作以同步的流程表達出來。使用 Promise 主要有以下好處:

  • 可以很好地解決回調地獄的問題(避免了層層嵌套的回調函數)。

  • 語法非常簡潔。Promise 對象提供了簡潔的API,使得控制非同步操作更加容易。

回調地獄的舉例

假設買菜、做飯、洗碗都是非同步的。

但真實的場景中,實際的操作流程是:買菜成功之後,才能開始做飯。做飯成功後,才能開始洗碗。這裡面就涉及到了多層嵌套調用,也就是回調地獄。

Promise 的基本用法

(1)使用new實例化一個Promise對象,Promise的構造函數中傳遞一個參數。這個參數是一個函數,該函數用於處理非同步任務。

(2)並且傳入兩個參數:resolve和reject,分別表示非同步執行成功後的回調函數和非同步執行失敗後的回調函數;

(3)通過 promise.then() 處理返回結果。這裡的 p 指的是 Promise實例。

代碼舉例如下:

<!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) => {
                // 這裡做非同步任務(比如ajax 請求介面。這裡暫時用定時器代替)
                setTimeout(function() {
                    var data = { retCode: 0, msg: 'qianguyihao' }; // 介面返回的數據
                    if (data.retCode == 0) {
                        // 介面請求成功時調用
                        resolve(data);
                    } else {
                        // 介面請求失敗時調用
                        reject({ retCode: -1, msg: 'network error' });
                    }
                }, 100);
            });

            // 第二步:業務層的介面調用。這裡的 data 就是 從 resolve 和 reject 傳過來的,也就是從介面拿到的數據
            promise.then(data => {
                // 從 resolve 獲取正常結果
                console.log(data);
            }).catch(data => {
                // 從 reject 獲取異常結果
                console.log(data);
            });
        </script>
    </body>
</html>

上方代碼中,當從介面返回的數據data.retCode的值不同時,可能會走 resolve,也可能會走 reject,這個由你自己的業務決定。

promise對象的3個狀態(瞭解即可)

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

  • 成功狀態:fullfilled

  • 失敗狀態:rejected

(1)當new Promise()執行之後,promise對象的狀態會被初始化為pending,這個狀態是初始化狀態。new Promise()這行代碼,括弧里的內容是同步執行的。括弧里定義一個function,function有兩個參數:resolve和reject。如下:

  • 如果請求成功了,則執行resolve(),此時,promise的狀態會被自動修改為fullfilled。

  • 如果請求失敗了,則執行reject(),此時,promise的狀態會被自動修改為rejected

(2)promise.then()方法,括弧裡面有兩個參數,分別代表兩個函數 function1 和 function2:

  • 如果promise的狀態為fullfilled(意思是:如果請求成功),則執行function1里的內容

  • 如果promise的狀態為rejected(意思是,如果請求失敗),則執行function2里的內容

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

完整代碼舉例如下:

    let promise = new Promise((resolve, reject) => {
        //進來之後,狀態為pending
        console.log('111');  //這行代碼是同步的
        //開始執行非同步操作(這裡開始,寫非同步的代碼,比如ajax請求 or 開啟定時器)
        if (非同步的ajax請求成功) {
            console.log('333');
            resolve('haha');//如果請求成功了,請寫resolve(),此時,promise的狀態會被自動修改為fullfilled
        } else {
            reject('555');//如果請求失敗了,請寫reject(),此時,promise的狀態會被自動修改為rejected
        }
    })
    console.log('222');

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

        }
    )

基於 Promise 處理 多次 Ajax 請求(鏈式調用)【重要】

實際開發中,我們經常需要同時請求多個介面。比如說:在請求完介面1的數據data1之後,需要根據data1的數據,繼續請求介面2,獲取data2;然後根據data2的數據,繼續請求介面3。

這種場景其實就是介面的多層嵌套調用。有了 promise之後,我們可以把多層嵌套調用按照線性的方式進行書寫,非常優雅。

也就是說:Promise 可以把原本的多層嵌套調用改進為鏈式調用

代碼舉例:(多次 Ajax請求,鏈式調用)

<!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發送Ajax請求
            */
            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 是從介面拿到的數據
                        } else {
                            // 處理異常情況
                            reject('介面請求失敗');
                        }
                    };
                    xhr.responseType = 'json'; // 設置返回的數據類型
                    xhr.open('get', url);
                    xhr.send(null); // 請求介面
                });
                return promise;
            }
            // 發送多個ajax請求並且保證順序
            queryData('http://localhost:3000/api1')
                .then(
                    data1 => {
                        console.log(JSON.stringify(data1));
                        // 請求完介面1後,繼續請求介面2
                        return queryData('http://localhost:3000/api2');
                    },
                    error1 => {
                        console.log(error1);
                    }
                )
                .then(
                    data2 => {
                        console.log(JSON.stringify(data2));
                        // 請求完介面2後,繼續請求介面3
                        return queryData('http://localhost:3000/api3');
                    },
                    error2 => {
                        console.log(error2);
                    }
                )
                .then(
                    data3 => {
                        // 獲取介面3返回的數據
                        console.log(JSON.stringify(data3));
                    },
                    error3 => {
                        console.log(error3);
                    }
                );
        </script>
    </body>
</html>

上面這個舉例很經典,需要多看幾遍。

return 的函數返回值

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

  • 情況1:返回 Promise 實例對象。返回的該實例對象會調用下一個 then。

  • 情況2:返回普通值。返回的普通值會直接傳遞給下一個then,通過 then 參數中函數的參數接收該值。

我們針對上面這兩種情況,詳細解釋一下。

情況1:返回 Promise 實例對象

舉例如下:(這個例子,跟上一段 Ajax 鏈式調用 的例子差不多)

<!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發送Ajax請求
            */
            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('介面請求失敗');
                        }
                    };
                    xhr.responseType = 'json'; // 設置返回的數據類型
                    xhr.open('get', url);
                    xhr.send(null); // 請求介面
                });
            }
            // 發送多個ajax請求並且保證順序
            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 實例對象
                        return new Promise((resolve, reject) => {
                            resolve('qianguyihao');
                        });
                    },
                    error2 => {
                        console.log(error2);
                    }
                )
                .then(data3 => {
                    console.log(data3);
                });
        </script>
    </body>
</html>

情況2:返回 普通值

<!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發送Ajax請求
            */
            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('介面請求失敗');
                        }
                    };
                    xhr.responseType = 'json'; // 設置返回的數據類型
                    xhr.open('get', url);
                    xhr.send(null); // 請求介面
                });
            }
            // 發送多個ajax請求並且保證順序
            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 是誰來調用呢?
                    答案是:這裡會產生一個新的 預設的 promise實例,來調用這裡的then,確保可以繼續進行鏈式操作。
                */
                .then(data3 => {
                    // 這裡的 data3 接收的是 普通值 'qianguyihao'
                    console.log(data3);
                });
        </script>
    </body>
</html>

Promise 的常用API:實例方法【重要】

Promise 自帶的API提供瞭如下實例方法:

  • promise.then():獲取非同步任務的正常結果。

  • promise.catch():獲取非同步任務的異常結果。

  • promise.finaly():非同步任務無論成功與否,都會執行。

代碼舉例如下。

寫法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' }; // 介面返回的數據
                        if (data.retCode == 0) {
                            // 介面請求成功時調用
                            resolve(data);
                        } else {
                            // 介面請求失敗時調用
                            reject({ retCode: -1, msg: 'network error' });
                        }
                    }, 100);
                });
            }

            queryData()
                .then(data => {
                    // 從 resolve 獲取正常結果
                    console.log('介面請求成功時,走這裡');
                    console.log(data);
                })
                .catch(data => {
                    // 從 reject 獲取異常結果
                    console.log('介面請求失敗時,走這裡');
                    console.log(data);
                })
                .finally(() => {
                    console.log('無論介面請求成功與否,都會走這裡');
                });
        </script>
    </body>
</html>

寫法2:(和上面的寫法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' }; // 介面返回的數據
                        if (data.retCode == 0) {
                            // 介面請求成功時調用
                            resolve(data);
                        } else {
                            // 介面請求失敗時調用
                            reject({ retCode: -1, msg: 'network error' });
                        }
                    }, 100);
                });
            }

            queryData()
                .then(
                    data => {
                        // 從 resolve 獲取正常結果
                        console.log('介面請求成功時,走這裡');
                        console.log(data);
                    },
                    data => {
                        // 從 reject 獲取異常結果
                        console.log('介面請求失敗時,走這裡');
                        console.log(data);
                    }
                )
                .finally(() => {
                    console.log('無論介面請求成功與否,都會走這裡');
                });
        </script>
    </body>
</html>

註意:寫法1和寫法2的作用是完全等價的。只不過,寫法2是把 catch 裡面的代碼作為 then裡面的第二個參數而已。

Promise 的常用API:對象方法【重要】

Promise 自帶的API提供瞭如下對象方法:

  • Promise.all():併發處理多個非同步任務,所有任務都執行成功,才能得到結果。

  • Promise.race(): 併發處理多個非同步任務,只要有一個任務執行成功,就能得到結果。

下麵來詳細介紹。

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 介面調用
            */
            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('伺服器錯誤');
                        }
                    };
                    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 介面調用
            */
            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('伺服器錯誤');
                        }
                    };
                    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>

瞭解這些內容之後, Promise 的基本用法,你就已經掌握了。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 情況: A機上有SSMS 18.x, B機上有SQL Server 2008 R2資料庫, C機上有Oracle Database 11.2.0.4.0資料庫 我想在A機用ssms連C機的oracle,一番瞭解,普遍做法是裝ODAC xcopy包,裡面有oledb組件,然後就可以在sqlserver ...
  • 1.簡介 當一個表數據量很大時候,很自然我們就會想到將表拆分成很多小表,在執行查詢時候就到各個小表去查,最後彙總數據集返回給調用者加快查詢速度。比如電商平臺訂單表,庫存表,由於長年累月讀寫較多,積累數據都是異常龐大的,這時候,我們可以想到表分區這個做法,降低運維和維護成本,提高讀寫性能。比如將前半年 ...
  • 前言: 對於任意一個系統而言,做好監控都是非常重要的,HBase也不例外。經常,我們會從JMX中獲取相關指標來做展示、對HBase進行監控,那這些指標是怎麼生成的呢?如果你想自定義自己的監控指標又該怎麼做呢?基於好奇之心和學習的目的,最近打算學習一下HBase監控相關原理及實現,今天先簡單捋一捋思路 ...
  • 一、CSS書寫格式 1.行內樣式 可以直接將CSS代碼直接寫到開始標簽中 <div style="color:red">我是div</div> 2.內嵌樣式 可以在一對head標簽中寫上一堆style標簽,然後再style標簽中編寫CSS代碼 <head> <style> div{ color:re ...
  • 重置CSS樣式: 1 /* http://meyerweb.com/eric/tools/css/reset/ 2 v2.0 | 20110126 3 License: none (public domain) 4 */ 5 6 html, 7 body, 8 div, 9 span, 10 app ...
  • 分享JS如何向網頁中輸入內容,如何與瀏覽器視窗進行交互,通過簡單的對象方法就可以輕鬆實現。 2-1JavaScript-輸出內容(document.write) 2-2JavaScript-警告(alert消息對話框) 2-3JavaScript-確認(confirm消息對話... ...
  • 前端和後端哪個工資高?事實上,兩個都是屬於技術研發崗位,都是高薪有前途的職業,不存在說哪個工資更高些,都基本在一萬到五萬之間,工資的差別主要體現在個人技術上。要問做前端好還是做後端好?其實無論做前端還是做後端,只要有實力,其實差別並不大。主要還是要看你喜歡哪個,適合哪個。 前端工作內容: 前端開發主 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...