天天看點

十二、Promise的學習筆記(Promise的基本使用、鍊式程式設計、all())

一、認識Promise

ES6中一個非常重要和好用的特性就是Promise

  • 但是初次接觸Promise會一臉懵逼,這TM是什麼東西?
  • 看看官方或者一些文章對它的介紹和用法,也是一頭霧水。

Promise到底是做什麼的呢?

  • Promise是異步程式設計的一種解決方案。

那什麼時候我們會來處理異步事件呢?

  • 一種很常見的場景應該就是網絡請求了。
  • 我們封裝一個網絡請求的函數,因為不能立即拿到結果,是以不能像簡單的3+4=7一樣将結果傳回。
  • 是以往往我們會傳入另外一個函數,在資料請求成功時,将資料通過傳入的函數回調出去。
  • 如果隻是一個簡單的網絡請求,那麼這種方案不會給我們帶來很大的麻煩。

但是,當網絡請求非常複雜時,就會出現回調地獄。

OK,我以一個非常誇張的案例來說明。

我們來考慮下面的場景(有誇張的成分):

  • 我們需要通過一個url1從伺服器加載一個資料data1,data1中包含了下一個請求的url2
  • 我們需要通過data1取出url2,從伺服器加載資料data2,data2中包含了下一個請求的url3
  • 我們需要通過data2取出url3,從伺服器加載資料data3,data3中包含了下一個請求的url4
  • 發送網絡請求url4,擷取最終的資料data4
十二、Promise的學習筆記(Promise的基本使用、鍊式程式設計、all())

上面的代碼有什麼問題嗎?

  • 正常情況下,不會有什麼問題,可以正常運作并且擷取我們想要的結果。
  • 但是,這樣額代碼難看而且不容易維護。

我們更加期望的是一種更加優雅的方式來進行這種異步操作。

如何做呢?

  • 就是使用Promise。
  • Promise可以以一種非常優雅的方式來解決這個問題。

二、Promise的基本使用

2.1 定時器的異步事件

我們先來看看Promise最基本的文法。

這裡,我們用一個定時器來模拟異步事件:

  • 假設下面的data是從網絡上1秒後請求的資料
  • console.log就是我們的處理方式。
    十二、Promise的學習筆記(Promise的基本使用、鍊式程式設計、all())

上圖是我們過去的處理方式,我們将它換成Promise代碼:

十二、Promise的學習筆記(Promise的基本使用、鍊式程式設計、all())

這個例子會讓我們感覺脫褲放屁,多此一舉

  • 首先,下面的Promise代碼明顯比上面的代碼看起來還要複雜。
  • 其次,下面的Promise代碼中包含的resolve、reject、then、catch都是些什麼東西?

我們先不管第一個複雜度的問題,因為這樣的一個屁大點的程式根本看不出來Promise真正的作用。

2.2 定時器異步事件解析

我們先來認認真真的讀一讀這個程式到底做了什麼?

  • new Promise很明顯是建立一個Promise對象
  • 小括号中((resolve, reject) => {})也很明顯就是一個函數,而且我們這裡用的是之前剛剛學習過的箭頭函數。

但是resolve, reject它們是什麼呢?

  • 我們先知道一個事實:在建立Promise時,傳入的這個箭頭函數是固定的(一般我們都會這樣寫)
  • resolve和reject它們兩個也是函數,通常情況下,我們會根據請求資料的成功和失敗來決定調用哪一個。

成功還是失敗?

  • 如果是成功的,那麼通常我們會調用resolve(messsage),這個時候,我們後續的then會被回調。
  • 如果是失敗的,那麼通常我們會調用reject(error),這個時候,我們後續的catch會被回調。

OK,這就是Promise最基本的使用了。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<script>
    // 1.使用setTimeout
    // setTimeout(() => {
    //     console.log('Hello world!');
    // }, 1000)


    // new Promise(參數) 參數->函數(resolve,reject)
    // resolve,reject它們本身又是函數
    // 鍊式程式設計
    /*new Promise((resolve,reject) => {
        // 第一次發生網絡請求的代碼
        setTimeout(() => {
            // console.log('Hello world!');
            resolve()
        }, 1000)
    }).then(() => {
        // 第一次拿到結果後的處理代碼
        console.log('Hello world!');
        console.log('Hello world!');

        return new Promise((resolve, reject) => {
            // 第二次發生網絡請求的代碼
            setTimeout(() => {
                resolve()
            }, 1000)
        }).then(() => {
            // 第二次拿到結果後的處理代碼
            console.log('Hello vuejs!');
            console.log('Hello vuejs!');

            return new Promise((resolve, reject) => {
                // 第三次發生網絡請求的代碼
                setTimeout(() => {
                    resolve()
                }, 1000)
            }).then(() => {
                // 第三次拿到結果後的處理代碼
                console.log('Hello promise!');
                console.log('Hello promise!');
            })
        })
    })*/

    // 什麼情況下會用到Promise?
    // 一般情況下是有異步操作時,會使用Promise對這個異步操作進行封裝
    new Promise((resolve, reject) => {
        setTimeout(() => {
            // 成功的時候調用resolve
            // resolve('hello data')

            // 失敗的時候調用reject
            reject('error message')
        }, 1000)
    }).then((data) => {
        console.log(data);
        console.log(data);
        console.log(data);
        console.log(data);
    }).catch((err) => {
        console.log(err);
    })

</script>
</body>
</html>
           
  1. then()處理成功,catch()處理失敗:
    十二、Promise的學習筆記(Promise的基本使用、鍊式程式設計、all())
  2. 不寫catch(),把處理成功和失敗的兩個函數都作為then()函數的參數傳進去:
    十二、Promise的學習筆記(Promise的基本使用、鍊式程式設計、all())

三、Promise三種狀态

首先, 當我們開發中有異步操作時, 就可以給異步操作包裝一個Promise

異步操作之後會有三種狀态,我們一起來看一下這三種狀态:

十二、Promise的學習筆記(Promise的基本使用、鍊式程式設計、all())
  1. pending:等待狀态,比如正在進行網絡請求,或者定時器沒有到時間。
  2. fulfill:滿足狀态,當我們主動回調了resolve時,就處于該狀态,并且會回調.then()
  3. reject:拒絕狀态,當我們主動回調了reject時,就處于該狀态,并且會回調.catch()
十二、Promise的學習筆記(Promise的基本使用、鍊式程式設計、all())

四、Promise的鍊式程式設計

十二、Promise的學習筆記(Promise的基本使用、鍊式程式設計、all())
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<script>
    // 1.使用setTimeout
    // setTimeout(() => {
    //     console.log('Hello world!');
    // }, 1000)


    // new Promise(參數) 參數->函數(resolve,reject)
    // resolve,reject它們本身又是函數
    // 鍊式程式設計
    new Promise((resolve,reject) => {
        // 第一次發生網絡請求的代碼
        setTimeout(() => {
            // console.log('Hello world!');
            resolve()
        }, 1000)
    }).then(() => {
        // 第一次拿到結果後的處理代碼
        console.log('Hello world!');
        console.log('Hello world!');

        return new Promise((resolve, reject) => {
            // 第二次發生網絡請求的代碼
            setTimeout(() => {
                resolve()
            }, 1000)
        }).then(() => {
            // 第二次拿到結果後的處理代碼
            console.log('Hello vuejs!');
            console.log('Hello vuejs!');

            return new Promise((resolve, reject) => {
                // 第三次發生網絡請求的代碼
                setTimeout(() => {
                    resolve()
                }, 1000)
            }).then(() => {
                // 第三次拿到結果後的處理代碼
                console.log('Hello promise!');
                console.log('Hello promise!');
            })
        })
    })
</script>
</body>
</html>
           
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<script>
    // 網絡請求: aaa -> 處理代碼(10行代碼)
    // 處理:aaa111 -> 處理代碼(10行代碼)
    // 處理:aaa111222 -> 處理代碼(10行代碼)

    // new Promise((resolve, reject) => {})
    /* new Promise((resolve, reject) => {
         setTimeout(() => {
             resolve('aaa')
         }, 1000)
     }).then(res => {
         // 1.自己處理10行代碼
         console.log(res, '第一層的10行處理代碼')

         // 2.對結果進行第一次處理
         return new Promise((resolve, reject) => {
             resolve(res + '111')
         })

     }).then(res => {
         console.log(res, '第二層的10處理代碼');

         return new Promise((resolve, reject) => {
             resolve(res + '222')
         })

     }).then(res => {
         console.log(res, '第三層的10處理代碼');
     })
 */

    // 2.
    // new Promise((resolve, reject) => {
    //     setTimeout(() => {
    //         resolve('aaa')
    //     }, 1000)
    // }).then(res => {
    //     // 1.自己處理10行代碼
    //     console.log(res,'第一層的10行處理代碼')
    //
    //     // 2.對結果進行第一次處理
    //     // return new Promise((resolve, reject) => {
    //     //         resolve(res + '111')
    //     //     })
    //     return Promise.resolve(res + '111')
    //     }).then(res => {
    //         console.log(res, '第二層的10處理代碼');
    //
    //         return Promise.resolve(res + '222')
    //
    //         }).then(res => {
    //             console.log(res, '第三層的10處理代碼');
    //         })

    // 鍊式調用中間某一層出現reject的情況,會直接執行最後面的catch()
    // new Promise((resolve, reject) => {
    //     setTimeout(() => {
    //         resolve('aaa')
    //     }, 1000)
    // }).then(res => {
    //     // 1.自己處理10行代碼
    //     console.log(res, '第一層的10行處理代碼')
    //
    //     // 2.對結果進行第一次處理
    //     // return new Promise((resolve, reject) => {
    //     //         resolve(res + '111')
    //     //     })
    //     return Promise.reject('error message')
    // }).then(res => {
    //     console.log(res, '第二層的10處理代碼');
    //
    //     return Promise.resolve(res + '222')
    //
    // }).then(res => {
    //     console.log(res, '第三層的10處理代碼');
    // }).catch(err => {
    //     console.log(err);
    // })

   // throw
    new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('aaa')
        }, 1000)
    }).then(res => {
        // 1.自己處理10行代碼
        console.log(res, '第一層的10行處理代碼')

        // 2.對結果進行第一次處理
        // return new Promise((resolve, reject) => {
        //         resolve(res + '111')
        //     })
        throw 'error message'
    }).then(res => {
        console.log(res, '第二層的10處理代碼');

        return Promise.resolve(res + '222')

    }).then(res => {
        console.log(res, '第三層的10處理代碼');
    }).catch(err => {
        console.log(err);
    })


    // 3.省略掉Promise.resolve
    // new Promise((resolve, reject) => {
    //     setTimeout(() => {
    //         resolve('aaa')
    //     }, 1000)
    // }).then(res => {
    //     // 1.自己處理10行代碼
    //     console.log(res, '第一層的10行處理代碼')
    //
    //     return res + '111'
    // }).then(res => {
    //     console.log(res, '第二層的10處理代碼');
    //
    //     return res + '222'
    //
    // }).then(res => {
    //     console.log(res, '第三層的10處理代碼');
    // })


</script>
</body>
</html>
           

4.1 鍊式調用

  • 我們在看Promise的流程圖時,發現無論是then還是catch都可以傳回一個Promise對象。
  • 是以,我們的代碼其實是可以進行鍊式調用的:
    十二、Promise的學習筆記(Promise的基本使用、鍊式程式設計、all())
  • 這裡我們直接通過Promise包裝了一下新的資料,将Promise對象傳回了
  1. Promise.resovle():将資料包裝成Promise對象,并且在内部回調resolve()函數
  2. Promise.reject():将資料包裝成Promise對象,并且在内部回調reject()函數

4.2 鍊式調用簡寫

簡化版代碼:

如果我們希望資料直接包裝成Promise.resolve,那麼在then中可以直接傳回資料

  • 注意下面的代碼中,我将

    return Promise.resovle(data)

    改成了

    return data

    ,結果依然是一樣的
十二、Promise的學習筆記(Promise的基本使用、鍊式程式設計、all())

4.3 all方法的使用

當需要多個網絡請求都成功後才能進行下一步操作時,可以使用Promise的all方法:

十二、Promise的學習筆記(Promise的基本使用、鍊式程式設計、all())
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<script>
    // Promise.all([
        // new Promise((resolve, reject) => {
        //     $ajax({
        //         url: 'url1',
        //         success: function (data) {
        //             resolve(data)
        //         }
        //     })
        // }),
        //
        // new Promise((resolve, reject) => {
        //     $ajax({
        //         url: 'url2',
        //         success: function (data) {
        //             resolve(data)
        //         }
        //     })
        // })
    Promise.all([
        new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve('result1')
            },2000)
        }),

        new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve('result2')
            },1000)
        })

    ]).then(results => {
        console.log(results);
    })

</script>
</body>
</html>