promise是什么意思?一首歌帶你搞懂Promise的含義

Promise 是異步編程的一種解決方案,比傳統的解決方案——回調函數和事件——更合理和更強大。(摘自<ECMAScript 6 入門>??,但是對于Promise的創建和執行流程,其實有很多人初學時,容易走入誤區。

今天就以一首歌來帶大家學習Promise。

Promise創建

Promise翻譯過來的意思就是”承諾”,其實Promise的創建本質就是一個許下承諾的過程,想到這我的腦海中不由得浮現了一首經典老歌,海鳴威 《你的承諾》。

歌中相知相戀的兩個人,奈何情深緣淺,終于還是分開,許下了“不為彼此難過, 過各自的生活”這樣的承諾。但是隨后歌曲又以”Oh baby 你答應我的我都記得 ,但是你卻忘了你的承諾 ,不是說好彼此都不再聯絡, 誰都別再犯錯”,暗示承諾的狀態發生改變。

由此不難想象以下兩種情況:

  1. 遵守承諾
promise是什么意思?一首歌帶你搞懂Promise的含義
  1. 違背承諾
promise是什么意思?一首歌帶你搞懂Promise的含義

同樣,JS中的Promise的創建也是許下代碼承諾的一種方式,它默認也包含三種狀態:

  1. pending (進行中)
  2. fulfilled(已成功)
  3. rejected(已失敗)

那么如何許下JS中的承諾呢:

var p = new Promise(function(){
 // 回調函數
})

Promise中接收一個回調函數(簡單說就是一個容器),傳入某個未來才會結束的事件(通常是一個異步操作)的結果;此時打印,你會得到一個Promise實例(實例化對象),展開之后默認顯示:

Promise {<pending>}
 [[Prototype]]: Promise,
 [[PromiseState]]: "pending",
 [[PromiseResult]]: undefined,

此時我們會看到

[[PromiseState]]用于存儲Promise實例(當前承諾)的狀態 ,默認顯示pending ;
[[PromiseResult]]用于表示Promise實例中存儲的數據 , 默認顯示undefined;
[[Prototype]]表示原型 以后再做講解

但是有人就會問了,既然有三種狀態,而默認是pending(進行中)狀態,那么我們如何將當前Promise實例的狀態改為另外兩種:

這就不得不提到Promise的第一特征了:

對象的狀態不受外界影響。Promise對象代表一個異步操作,有三種狀態:pending(進行中)、fulfilled(已成功)和rejected(已失敗)。只有異步操作的結果,可以決定當前是哪一種狀態,任何其他操作都無法改變這個狀態。

可以理解為,狀態并不是自行改變的,而是由異步(同步也可以)的結果決定了.。

所以Promise在創建過程中,傳入的回調函數接收了改變狀態的兩個方法 ,resolve,reject作為形式參數傳入.

var p = new Promise(function(resolve,reject){//形式參數
 // 回調函數
})

resolve() 將Promise實例的狀態 由pending(進行中) 改為 fulfilled(已成功)

reject() 將Promise實例的狀態 由pending(進行中) 改為rejected(已失敗)

而且resolve() reject() 方法在調用時 還可以接收一個參數,作為數據傳入,存儲到Promise實例中。

同步代碼展示:

var p = new Promise(function(resolve,reject){//形式參數
 // 回調函數
 resolve(111)
})
console.log(p)
結果:
Promise {<fulfilled>: 111}
    [[Prototype]]: Promise
    [[PromiseState]]: "fulfilled"   // 已成功
    [[PromiseResult]]: 111          // 存儲數據
    
var p = new Promise(function (resolve, reject) {//形式參數
    // 回調函數
    reject(222)
})
Promise {<rejected>: 111}
 [[Prototype]]: Promise
 [[PromiseState]]: "rejected"   // 已失敗
 [[PromiseResult]]: 222         // 存儲數據
 
 Uncaught (in promise) 111         // 報錯 (可以不用理會  后面會被then() / catch() 方法捕獲)

resolve() 和 reject() 方法,在同步代碼中調用,會立即改變Promise實例的狀態。

異步代碼展示:

var p = new Promise(function (resolve, reject) {//形式參數
    setTimeout(function () {
        resolve(111);
        console.log("line 24:", p);
    }, 2000)
})
console.log("line 27:", p);

結果依次為:
line 27: Promise {<pending>}
line 24: Promise {<fulfilled>: 111}

-------------------------------------兩次代碼請分開執行-------------------------------------------------

var p = new Promise(function (resolve, reject) {//形式參數
    setTimeout(function () {
        reject(111);
        console.log("line 24:", p);
    }, 2000)
})
console.log("line 27:", p);

結果依次為:
line 27: Promise {<pending>}
...2s后
line 24: Promise {<rejected>: 111}
報錯: 2.html:60 Uncaught (in promise) 111    (可以不用理會  后面會被then() / catch() 方法捕獲)

resolv e() 和 reject() 方法,在異步代碼中調用:由于JS是單線程,會優先在主線程執行同步代碼,異步代碼會放到任務隊列中,所以在頁面加載時,,Promise實例為pending(進行中)狀態,等待主線程執行完畢,任務隊列通知主線程,異步任務可以執行了,該任務才會進入主線程執行。

此時setTimeout中的回調函數被調用,執行了resolve() 或 reject() 方法, Promise實例會隨之改變狀態,并存儲傳入的數據。

那么resolve() reject() 方法,可以重復調用么?

這就要講到Promise的第二大特征:一旦狀態改變,就不會再變,任何時候都可以得到這個結果。Promise對象的狀態改變,只有兩種可能:從pending變為fulfilled和從pending變為rejected。只要這兩種情況發生,狀態就凝固了,不會再變了,會一直保持這個結果,這時就稱為 resolved(已定型) 注意,為了行文方便,本章后面的resolved統一只指fulfilled狀態,不包含rejected狀態。

由此可見Promise本質,就是一個狀態機,當該Promise對象創建出來之后,其狀態就是pending(進行中),然后通過程序來控制到底是執行已完成,還是執行已失敗。

因為Promise處理的是異步任務,所以我們還得對Promise做監聽,當Promise的狀態發生變化時,我們要執行相應的函數(then catch finally)。

Promise的動態方法

所謂Promise動態方法,即將封裝的方法存儲在Promise的原型對象(prototype)上,供所有通過構造函數創建的實例化對象使用。

Promise.prototype.then()

Promise 實例具有then方法,它的作用是為 Promise 實例添加狀態改變時的回調函數,前面說過 Promise實例的狀態可以從pending(進行中) 變為 fulfilled(已成功)或rejected(已失敗),所以then方法中接收兩個回調函數(它們都是可選的)。

p.then(resolveHandler,rejectHandler)

resolveHandler 第一參數,用于指定Promise實例,由pending(進行中) 變為 fulfilled(已成功)時執行的回調函數。

rejectHandler 第二參數,用于指定Promise實例,由pending(進行中) 變為 rejected(已失敗)時執行的回調函數。

可以理解為 then()方法中的兩個回調函數,提前規定好了狀態改變時需要執行的內容,等待以后Promise實例的狀態之后再執行對應的回調函數 (本質還是回調函數,要用魔法打敗魔法)。

注意:resolveHandler rejectHandler 中可以傳入一個形式參數 接收Promise實例中存儲數據。

// 等待兩秒后 隨機一個0-1的數,如果 num>=0.5 就變為fulfilled,否則變為rejected
var p = new Promise(function (resolve, reject) {//形式參數
        setTimeout(function () {
            var num = Math.random();
            if (num >= 0.5) {
                resolve(111)
            } else {
                reject(2222);
            }
            console.log("line 24:", p);
        }, 2000)
    })
console.log("line 27:", p);

p.then(function (arg) {
 console.log("line 50", arg);
}, function (arg) {
 console.log("line 52", arg);
})

情況1: (num>=0.5)  
    line 27: Promise {<pending>}
    ...2秒后
    line 24: Promise {<fulfilled>: 111}
    line 50 111
    
情況2: (num<0.5) 
 line 27: Promise {<pending>}
 ...2秒后
    2.html:81 line 24: Promise {<rejected>: 2222}
    2.html:89 line 52 2222

解析:在異步代碼中調用: 由于JS是單線程,會優先在主線程執行同步代碼,異步代碼會放到任務隊列中,所以

  1. Promise創建完畢,Promise實例為pending(進行中)狀態;
  2. 調用 then()方法,傳入兩個回調函數,提前規定好了狀態改變時需要執行的內容,等待以后Promise實例的狀態之后再執行對應的回調函數;
  3. 等待主線程執行完畢,任務隊列通知主線程,異步任務可以執行了,該任務才會進入主線程執行。=> 隨機一個0-1的隨機數,根據結果改變Promise實例的狀態 ,存儲傳入的數據 => 執行then方法中對應的回調函數。

還有一個有趣的地方:then方法返回的是一個新的Promise實例(注意,不是原來那個Promise實例)。因此可以采用鏈式寫法,即then方法后面再調用另一個then方法。

    var p = new Promise(function (resolve, reject) {//形式參數
        setTimeout(function () {
            resolve(1);
        }, 2000);
    })

    p.then(function (num) {
        console.log("line 64:", num);
        return 2;
    }).then(function (num) {
        console.log("line 67:", num);
        return 2;
    })
    結果:
    line 64: 1
    line 67: 2

上面的代碼使用then方法,依次指定了兩個回調函數。第一個回調函數完成以后,會將返回結果作為參數,傳入第二個回調函數。當然這里有一個前提,那就是Promise實例的狀態為已成功且代碼執行過程中不出錯的情況下。萬一出錯的話,那我們就可以使用then()方法的第二個回調函數或者catch()方法捕獲錯誤。

Promise.prototype.catch()

catch()方法是.then(null, rejection)或.then(undefined, rejection)的別名,用于指定發生錯誤時的回調函數。

 function readText(url) {
        var p = new Promise(function (resolve, reject) {
            $.ajax({
                type: "get",
                url: url,
                success: function (text) {
                    resolve(text);
                },
                error:function(err){
                 reject(err);
                }
            })
        })
        return p; // {pending}
    }
    readText("../data/1.txt").then(res => {
        console.log("line 93", res)
    }).catch(err => {
        console.log(err);
    })

上面代碼中,readText()方法返回一個 Promise 對象,如果該對象狀態變為resolved,則會調用then()方法指定的回調函數;如果異步操作拋出錯誤,狀態就會變為rejected,就會調用catch()方法指定的回調函數,處理這個錯誤。另外,then()方法指定的回調函數,如果運行中拋出錯誤,也會被catch()方法捕獲。

then和catch的鏈式操作

then()和catch()配合使用,實現的鏈式操作效果更佳。

    var p = new Promise(function (resolve, reject) {
        setTimeout(function () {
            resolve(1);
        }, 2000);
    })

    console.log("line 21", p);
    p.then(function (num) {
        console.log("line 23:", num);
        return 2;
    }).then(function (num) {
        console.log("line 25:", num);
    }).then(function (num) {
        console.log("line 28:", num);
    }).catch(function (err) {
        console.log(err);
    })
    
結果:
    line 21 Promise {<pending>}
    ...等待2s后
    line 23: 1
    line 25: 2
    line 28: 3

上面的代碼使用then catch方法進行鏈式調用,依次指定了三個then()的回調函數以及一個catch()的回調函數。第一個回調函數完成以后,會將返回結果作為參數,傳入第二個回調函數,同樣第二個回調函數完成后,會將返回結果作為參數,傳入第三個回調函數,以此類推。當然這里同樣也有一個前提,那就是Promise實例的狀態為已成功且代碼執行過程中不出錯的情況下。萬一出錯的話,那catch()方法剛好捕獲鏈式操作過程中出現的錯誤。

如果then方法中回調函數的返回值也是一個Promise實例;

function readText(url) {
        var p = new Promise(function (resolve, reject) {
            $.ajax({
                type: "get",
                url: url,
                success: function (text) {
                    resolve(text);
                },
                error: function (err) {
                    reject(err);
                }
            })
        })
        return p; // {pending}
    }

    // ajax恐怖回調的解決方式1:
    // T = T1 + T2 + T3   (時間)
    var str = "";
    readText("../data/1.txt").then(txt => {
        console.log("line 36", txt);
        str += txt;
        return readText("../data/2.txt");
    }).then(txt => {
        console.log("line 40", txt);
        str += txt;
        return readText("../data/3.txt");
    }).then(txt => {
        console.log("line 44", txt);
        str += txt;
        return str;
    }).then(txt => {
        console.log("line 48", txt);
    }).catch(err => {
        console.log("失敗:", err);
    })
    
結果:
    line 36 11111
  line 40 22222
 line 44 33333
 line 48 111112222233333  

上面代碼中,第一個then方法指定的回調函數,返回的是另一個Promise對象。這時,第二個then方法指定的回調函數,就會等待這個新的Promise對象狀態發生變化。如果變為resolved,繼續鏈式調用后面then方法中指定的回調函數,如果狀態變為rejected,就會被catch方法捕獲。

注意:Promise 實例的錯誤具有“冒泡”性質,會一直向后傳遞,直到被捕獲為止。也就是說,錯誤總是會被下一個catch語句捕獲。

Promise的靜態方法

所謂Promise靜態方法,即將封裝的方法存儲在構造函數Promise上,由構造函數自身調用。

Promise.all

Promise.all()方法用于將多個 Promise 實例,包裝成一個新的 Promise 實例。

const p = Promise.all([p1, p2, p3]);

相較于then方法的鏈式操作,Promise.all()更注重并發,即依次按順序發送多個請求,等待所有的請求均有結果之后,對應請求順序將數據依次存放到數組中返回。

模擬封裝(簡易版)
  Promise.myAll = function (list) {
        return new Promise(function (resolve, reject) {  // 返回一個新的Promise實例
            var arr = [];
            for (let i = 0; i < list.length; i++) {
                let p = list[i];// 每一個Promise實例(如果傳入的數據非Promise實例 會通過Promise.resolve轉化)
                
                // 提前規定好每一個實例 成功和失敗時執行的內容  => 請求有結果之后會執行
                p.then(res => {  //等待異步操作有結果之后 對應下標放到數組中
                    arr[i] = res;
                
                    if (arr.length === list.length) { // 所有的請求全都成功 => (也是一個異步操作)
                        resolve(arr);
                    }
                }).catch(err => {    //只要有一個失敗 就走catch  
                    console.log(err);
                    reject(err);   
                })
            }
        })
 }
 
 const p = Promise.myAll([p1, p2, p3]);   

通過以上代碼可以發現: (摘自ECMAScript 6 入門)

Promise.all()方法接受一個數組作為參數,p1、p2、p3都是 Promise 實例,如果不是,就會先調用下面講到的Promise.resolve方法,將參數轉為 Promise 實例,再進一步處理。另外,Promise.all()方法的參數可以不是數組,但必須具有 Iterator 接口,且返回的每個成員都是 Promise 實例。

p的狀態由p1、p2、p3決定,分成兩種情況。

(1)只有p1、p2、p3的狀態都變成fulfilled,p的狀態才會變成fulfilled,此時p1、p2、p3的返回值組成一個數組,傳遞給p的回調函數。

(2)只要p1、p2、p3之中有一個被rejected,p的狀態就變成rejected,此時第一個被reject的實例的返回值,會傳遞給p的回調函數。

Promise.race

Promise.race()方法同樣是將多個 Promise 實例,包裝成一個新的 Promise 實例。

const p = Promise.race([p1, p2, p3]);

相較于Promise.all方法, Promise.race方法更側重狀態改變的速度。

模擬封裝(簡易版)
 Promise.myRace = function (list) {
    return new Promise(function (resolve, reject) {  // 返回一個新的Promise實例
         for (let i = 0; i < list.length; i++) {
             let p = list[i];// 每一個Promise實例 (如果傳入的數據非Promise實例 會通過Promise.resolve轉化)
 
             // 提前規定好每一個實例 成功和失敗時執行的內容  => 請求有結果之后會執行
             p.then(res => {  //等待異步操作有結果之后 對應下標放到數組中
              resolve(res);
             }).catch(err => {
              reject(err);
             })
       }
    })
}

const p = Promise.myRace([p1, p2, p3]);

通過以上代碼可以發現: (摘自ECMAScript 6 入門)

只要p1、p2、p3之中有一個實例率先改變狀態,p的狀態就跟著改變。那個率先改變的 Promise 實例的返回值,就傳遞給p的回調函數。

Promise.allSettled

Promise.allSettled()方法同樣是將多個 Promise 實例,包裝成一個新的 Promise 實例。

const p = Promise.allSettled([p1, p2, p3]);

相較于Promise.all方法,Promise.allSettled跟注重于不管每一個Promise實例是成功還是失敗,都會將結果依次按請求順序放到數組中。

模擬封裝(簡易版)
 Promise.myAllSettled = function (list) {
         return new Promise(function (resolve, reject) {  // 返回一個新的Promise實例
             var arr = [];
             for (let i = 0; i < list.length; i++) {
                 let p = list[i];// 每一個Promise實例   (如果傳入的數據非Promise實例 會通過Promise.resolve轉化)
 
                 // 提前規定好每一個實例 成功和失敗時執行的內容  => 請求有結果之后會執行
                 p.then(res => {  //等待異步操作有結果之后 對應下標放到數組中
                     var obj = { status: "fulfilled", value: txt };
                     arr[i] = obj;
                     if (arr.length === list.length) { // 請求全都成功 => arr(異步)
                         resolve(arr);
                     }
                 }).catch(err => {
                     var obj = { status: "rejected", reason: err };
                     arr[i] = obj;
                     if (arr.length === list.length) { // 請求全都成功 => arr(異步)
                         resolve(arr);
                     }
                 })
             }
         })
     }
     const p =  Promise.myAllSettled([p1, p2, p3])

值得注意的地方時,在將數據放到數組的過程中,傳入的Promise實例的狀態不同,放置的結果稍有不同(數組中的每個成員是一個對象,對象的格式是固定的)。

// 異步操作成功時
{status: 'fulfilled', value: value}

// 異步操作失敗時
{status: 'rejected', reason: reason}

成員對象的status屬性的值只可能是字符串fulfilled或字符串rejected,用來區分異步操作是成功還是失敗。如果是成功(fulfilled),對象會有value屬性,如果是失敗(rejected),會有reason屬性,對應兩種狀態時前面異步操作的返回值。

總結

有了Promise對象,就可以將異步操作以同步操作的流程表達出來,避免了層層嵌套的回調函數。此外,Promise對象提供統一的接口,使得控制異步操作更加容易。

Promise也有一些缺點。首先,無法取消Promise,一旦新建它就會立即執行,無法中途取消。其次,如果不設置回調函數,Promise內部拋出的錯誤,不會反應到外部。第三,當處于pending狀態時,無法得知目前進展到哪一個階段(剛剛開始還是即將完成)。

升級用法 async await

ES2017 標準引入了 async 函數,使得異步操作變得更加方便。

async 函數是什么?一句話,它就是 Generator 函數的語法糖。

基本用法

 async function fn() {
        var str = "";
        var text = await readText("../data/1.txt"); // Promise實例
        console.log("line 29:",text);
        str += text;

        var text = await readText("../data/2.txt"); // Promise實例
        console.log("line 33:",text);
        str += text;

        var text = await readText("../data/3.txt"); // Promise實例
        console.log("line 37:",text);
        str += text;

        return str;
    }
    
    fn().then(res=>{
     console.log("line 44:",res)
    }).catch(err=>{
     console.log("line 46:",err)
    })
    
    結果:
    line 29: 11111
 line 33: 22222
 line 37: 33333
 line 44: 111112222233333   

async函數的返回值也是一個Promise實例,在async函數執行的過程中,一旦遇到await就會先返回pending(進行中)狀態的Promise實例,等待異步操作有結果之后,繼續執行await之后的語句,依次類推,語句全部執行完畢且無錯誤的情況下,則返回的Promise實例會變為已成功,否則會變為已失敗。

聲明:本文由網站用戶竹子發表,超夢電商平臺僅提供信息存儲服務,版權歸原作者所有。若發現本站文章存在版權問題,如發現文章、圖片等侵權行為,請聯系我們刪除。

(0)
上一篇 2022年11月28日 15:31:12
下一篇 2022年11月28日 15:35:29

相關推薦

發表回復

您的電子郵箱地址不會被公開。 必填項已用*標注

主站蜘蛛池模板: 国产dvd毛片在线视频| 国内揄拍国内精品| 久久这里只精品热免费99| 波多野结衣456| 午夜三级国产精品理论三级| 青青草视频ios| 国产猛烈高潮尖叫视频免费| 97日日碰曰曰摸日日澡 | 午夜精品福利在线| 韩国三级大全久久网站| 国产精品一卡二卡三卡| 97公开免费视频| 天天躁日日躁狠狠躁欧美老妇| 中文午夜人妻无码看片| 日日碰狠狠添天天爽超碰97| 久久精品国产亚洲av麻| 模特侨依琳大尺度流出| 亚洲日本人成中文字幕| 污污网站在线观看| 人妻仑乱A级毛片免费看| 窝窝午夜看片国产精品人体宴| 四虎精品成人免费永久| 超碰aⅴ人人做人人爽欧美| 国产大尺度吃奶无遮无挡网| 很黄很黄的网站免费的| 国产精品亚洲а∨无码播放不卡 | 欧美日韩国产一区二区 | 亚洲欧美成人一区二区在线电影| 男人j桶进女人p无遮挡在线观看| 再深点灬舒服灬太大了男小| 美女国产毛片a区内射| 国产a免费观看| 色三级大全高清视频在线观看| 国产乱人伦偷精精品视频| 麻豆xfplay国产在线观看| 国产成人综合亚洲绿色| 日本按摩xxxxx高清| 国产片91人成在线观看| 波多野结衣xfplay在线观看| 国产精品无码2021在线观看| 2021国产麻豆剧传媒仙踪林 |