首頁 > 軟體

JavaScript中的Promise詳解

2022-11-09 14:01:25

前言

Promise是ES6引入的非同步程式設計的新解決方案。語法上Promise是一個建構函式,用來封裝非同步操作並可以獲取其成功或失敗的結果。

Promise特點:

Promsie物件非同步操作有三種狀態,pending(進行中)、fulfilled(已成功)和reject(已失敗)。只有非同步操作才可以決定當前是哪種狀態;Promise狀態改變有兩種可能,從pending變為fulfidded和從pending變為rejected。狀態發生改變就不能再改變了,稱為:resolved(已定型)

Promise物件的作用:將非同步操作以同步操作的流程表達出來,避免層層巢狀的回撥函數,而且Promise提供了統一的介面,使得控制非同步操作更加容易。

Promise使用

Promise建構函式接收一個函數作為引數,該函數的兩個引數分別為:resolve 和 reject。

resolve:在非同步操作成功時呼叫,並將非同步操作的結果作為引數傳遞出去。

reject:在非同步操作失敗時呼叫,並將非同步操作報出的錯誤作為引數傳遞出去。

Promise範例生成後,可以用 then 方法分別指定 resolved 狀態和 rejected 狀態的回撥函數;而第一個回撥函數是 Promise 物件狀態變 resolved 時呼叫,第二個回撥函數是 Promise 物件的狀態變為 rejected 時呼叫。案例如下:

<script>
    // 範例化 Promise 物件,接收引數為函數型別值
    const p = new Promise(function(resolve,reject){
        // 封裝非同步操作
        setTimeout(function(){
            // 資料操作
            // let data = '使用者資料'
            // resolve
            // resolve(data)
            let err = '資料讀取失敗'
            reject(err)
        },1000)
    })
    // 呼叫 Promise 物件的 then 方法
    // 資料呼叫成功,則呼叫下面第一個回撥,失敗則是第二個
    p.then(function(value){//成功的形參
        console.log(value);
    },function(reason){//失敗的形參
        console.log(reason);
    })
</script>

Promise新建後會立即執行,然而 then 方法指定的回撥函數將在當前指令碼所有同步任務執行完才會執行。

<script>
    let p = new Promise(function(resolve,reject){
        console.log('People');
        // 資料操作
        let data = 'World'
        resolve(data)
    })
    p.then(function(value){
        console.log(value);
    })
    console.log('Hello');
</script>

Promise封裝Ajax請求

<script>
    const p = new Promise((resolve,reject) =>{
        // 1.建立物件
        const xhr = new XMLHttpRequest()
        // 2.初始化
        xhr.open("GET","https://ai.baidu.com/")
        // 3.傳送
        xhr.send()
        // 4.繫結事件,處理響應結果
        xhr.onreadystatechange = function(){
            // 判斷
            if(xhr.readyState ===4 ){
                // 判斷響應碼 200-299
                if(xhr.status >= 200 && xhr.status <300){
                    // 表示成功
                    resolve(xhr.response);
                }else{
                    // 如果失敗
                    reject(xhr.status);
                }
            }
        }
    })
    // 指定成功和失敗的回撥
    p.then(function(value){
        console.log(value);
    },function(reason){
        console.error(reason);
    })
</script>

Promise封裝讀取檔案

這裡藉助 Node.js 方法進行讀取檔案,不瞭解Node.js的方法可以關注我,後期會出相關專欄。

// 1.引入 fs 模組
const fs = require('fs')
// 2.呼叫方法讀取檔案
// fs.readFile('./index.md',(err,data)=>{
//     // 如果失敗,則丟擲錯誤
//     if(err) throw err;
//     // 如果沒有出錯,則輸出內容
//     console.log(data.toString());
// })
// 3.使用 Promise 封裝
const p = new Promise(function(resolve,reject){
    fs.readFile('./index.md',(err,data)=>{
        // 判斷如果失敗
        if(err) reject(err)
        // 如果成功
        resolve(data)
    })
})
p.then(function(value){
    console.log(value.toString());
},function(reason){
    console.log("讀取失敗!!");
})

Promise.prototype.then方法

Promise範例具有then方法,即then方法定義在原型物件Promise.prototype上,作用是為Promise範例新增狀態改變時的回撥函數。案例如下:

<script>
    // 建立 promise 兌現
    const p = new Promise((resolve,reject)=>{
        setTimeout(()=>{
            resolve('使用者資料')
            // reject('出錯啦')
        })
    })
    // 呼叫 then 方法 
    const result = p.then(value =>{
        // 如果回撥函數中返回的結果是非 promise 型別屬性,狀態為成功,返回值為物件的成功的值
        console.log(value);
        // 1、非promise型別的屬性
        // return 123
        // 2、是promise物件
        // return new Promise((resolve,reject)=>{
        //     // resolve('ok')
        //     // reject('error')
        // })
        // 3、丟擲錯誤
        throw new Error('出錯啦!!')
    },reason=>{
        console.log(reason);
    })
    // then方法的返回結果是 Promise 物件,物件狀態由回撥函數的執行結果決定
    console.log(result);
</script>

then方法返回的是一個新的Promise範例,因此可以採用鏈式寫法,即then方法後面再呼叫另一個then方法。但是前一個then()方法中的回撥函數中又可能返回一個Promise範例,這時候後面一個then()方法中的回撥函數會等前一個Promise範例狀態發生變化才會呼叫。案例如下:

<script>
    let p = new Promise((resolve, reject) => {
        setTimeout(()=>{
            resolve('success')
        });
    },1000);
    p.then(
        res => {
            console.log(res);
            return `${res} again`;
        }
        )
        .then(
            res => console.log(res)
    );
</script>

Promise多檔案讀取

回撥地獄與Promise物件實現相比,不會產生回撥現象,而且也不用再資料龐大時進行大規模的縮排。承接上文單檔案讀取,現在進行多檔案讀取,案例如下:

// 1.引入 fs 模組
const { rejects } = require('assert')
const fs = require('fs')
const { resolve } = require('path')
// 2.回撥地獄 呼叫方法讀取檔案
// fs.readFile('./index.md',(err,data)=>{
//     fs.readFile('./index1.md',(err,data1)=>{
//         fs.readFile('./index2.md',(err,data2)=>{
//             let result = data + 'rn' + data1 +'rn'+ data2
//             console.log(result);
//         })
//     })
// })
// 3.使用 Promise 實現
const p = new Promise((resolve,reject)=>{
    fs.readFile('./index.md',(err,data)=>{
        resolve(data)
    })
})
// value 是第一個檔案的內容
p.then(value => {
    return new Promise((resolve,reject)=>{
        fs.readFile('./index1.md',(err,data)=>{//data是第二個檔案的內容
            // 返回的是第一個和第二個檔案合併的陣列
            resolve([value, data])
        })
    })
}).then(value => {//這裡的value就是上面合併的陣列
    return new Promise((resolve,reject)=>{
        fs.readFile('./index2.md',(err,data)=>{//data是第三個檔案的內容
            // 壓入
            value.push(data)
            resolve(value)
        })
    })
}).then(value => {//如果上面成功,現在的value就是返回三個陣列的合集
    console.log(value.join('rn'));//陣列用join進行拼接
})

Promise.prototype.catch()

該方法用於指定發生錯誤時的回撥函數。舉個簡單的例子:

<script>
    const p = new Promise((resolve,reject)=>{
        setTimeout(function(){
            reject('出錯了!')
        },1000)
    })
    // p.then(value=>{},reason=>{
    //     console.log(reason);
    // })
    p.catch(reason=>{
        console.log(reason);
    })
</script>

Promise.prototype.finally()

finally()方法指定不管promise最後的狀態如何,在執行完then或catch指定的回撥函數以後,都會執行finally方法指定的回撥函數。

<script>
    const p = new Promise((resolve,reject)=>{
        setTimeout(function(){
            reject('出錯了!')
        },1000)
    })
    p.catch(reason=>{
        console.log(reason);
    }).finally(()=>{
        console.log('我是finall,不管promise結果如何我都要執行');
    })
</script>

Promise.all()

該方法用於將多個 Promise 範例包裝成一個新的 Promise 範例,方法接受一個陣列作為引數,陣列引數都是Promsie範例。當然引數也可以不是陣列,但必須有Iterator介面,且返回的每個成員都是Promise範例。

該方法只適合所有非同步都操作成功的情況,如果有一個操作失敗就無法滿足要求。

<script>
    // Promise.all()的狀態由引數決定:分以下兩種情況
    /* 
    * (1)Promise.all()引數的狀態都變成fulfilled,Promise.all()狀態才會變成fulfilled,此時所有引數的返回值組成一個陣列,傳遞給Promise.all()的回撥函數。
    * (2)只要引數之中有一個被rejected,Promise.all()的狀態就變成rejected,此時第一個被reject的範例的返回值,會傳遞給p的回撥函數。
    */
    const p1 = new Promise((resolve, reject) => {
        resolve('hello');
    })
    .then(result => result);
    const p2 = new Promise((resolve, reject) => {
        throw new Error('報錯了');
    })
    .then(result => result);
    Promise.all([p1, p2])
    .then(result => console.log(result))
    .catch(e => console.log(e));//Error:報錯了
</script>

Promise.race()

該方法同樣是將多個 Promise 範例包裝成一個新的 Promsie 範例,該方法與 Promise.all()方法一樣,區別是該方法中只要引數之中有一個範例率先改變狀態,該方法的範例狀態跟著改變,那個率先改變的 Promise 範例的返回值就傳遞給該方法範例的回撥函數。

<script>
    const p = Promise.race([
        fetch('./index.js'),
        new Promise(function (resolve, reject) {
        setTimeout(() => reject(new Error('request timeout')), 5000)
        })
    ]);
    p.then(console.log).catch(console.error);
</script>

上面程式碼中,如果 5 秒之內fetch方法無法返回結果,變數p的狀態就會變成rejected,從而觸發catch方法指定的回撥函數。

Promise.allSettled()

該方法用來確定一組非同步是否都結束(不管成功或失敗)。方法接受一個陣列作為引數,只有當引數陣列中所有 Promise物件 都發生變化,返回的 Promise 物件才會發生狀態變更。

<script>
    const resolved = Promise.resolve(42);
    const rejected = Promise.reject(-1);
    const allSettledPromise = Promise.allSettled([resolved, rejected]);
    allSettledPromise.then(function (results) {
        console.log(results);
    });
</script>

回撥函數接受到的引數是陣列results,該陣列的每一個成員都是一個物件,對應傳入Promise.allSettled()的陣列裡面的兩個 Promsie 物件。

Pomise.any()

Promise.any()和Promise.race()方法很像,唯一區別就是Promise.any()不會因為某個 Promise 變成 rejected 狀態而結束,必須等到所有引數 Promise 變成 rejected 狀態才會結束。

var resolved = Promise.resolve(42);
var rejected = Promise.reject(-1);
var alsoRejected = Promise.reject(Infinity);
Promise.any([resolved, rejected, alsoRejected]).then(function (result) {
  console.log(result); // 42
});
Promise.any([rejected, alsoRejected]).catch(function (results) {
  console.log(results instanceof AggregateError); // true
  console.log(results.errors); // [-1, Infinity]
});

Promise.resolve()

該方法能夠將現有物件轉換為 Promise 物件。

Promise.resolve('foo')
// 等價於
new Promise(resolve => resolve('foo'))

到此這篇關於JavaScript中的Promise詳解 的文章就介紹到這了,更多相關JS Promise內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com