原來你是這樣的Promise

来源:https://www.cnblogs.com/wind-lanyan/archive/2018/04/15/8849643.html
-Advertisement-
Play Games

1. Promise簡介 promise是非同步編程的一種解決方案,它出現的初衷是為瞭解決回調地獄的問題。 打個比方,我需要: --(延遲1s)--> 輸出1 --(延遲2s)--> 輸出2 --(延遲3s)--> 輸出3 通常寫法: setTimeout(()=> { console.log('1' ...


1. Promise簡介

promise是非同步編程的一種解決方案,它出現的初衷是為瞭解決回調地獄的問題。

打個比方,我需要:

--(延遲1s)--> 輸出1 --(延遲2s)--> 輸出2 --(延遲3s)--> 輸出3

通常寫法:

setTimeout(()=> {
    console.log('1');
    setTimeout(()=> {
        console.log('2');
        setTimeout(()=> {
            console.log('3'); 
        }, 3000)
    }, 2000)
}, 1000)

這樣的多重的嵌套的回調被稱為回調地獄,這樣的代碼可讀性很差,不利於理解。

如果用promise的話畫風一轉

function delay(time, num) {
    return new Promise((res, rej)=> {
        setTimeout(()=> {
            console.log(num);
            res();
        }, time*1000)
    });
}
delay(1, 1).then(()=> {
    return delay(2, 2);
}).then(()=> {
    delay(3, 3);
})
使用了promise的鏈式調用,代碼結構更清晰。

是不是很棒?那還不趕快get起來~

2. Promise的使用

調用方式如下:

new Promise((resolve, reject)=> {
    if('some option') {
        resolve('some value');
    } else {
        reject('some error');
    }
}).then(
    val=> {
        // ...
    },
    error=> {
        // ...
    }
)

Promise構造函數接收一個函數型參數fn,fn有兩個參數,分別是:resolve、reject,Promise還有一個Promise.prototype.then方法,該方法接收兩個參數,分別是成功的回調函數succ和失敗的回調函數error。

在fn中調用resolve會觸發then中的succ回調,調用reject會觸發error回調。

2.1 參數傳遞

  • 在fn內部調用resolve/reject傳入的參數會作為相應參數傳入相應的回調函數
    new Promise((res, rej)=> {
        res('happy')
    }).then(val=> {
        console.log(val);  // happy
    });
    
    new Promise((res, rej)=> {
        rej('error!');
    }).then(val=> {}, err=> {
        console.log(err);  // error!
    });
  • 鏈式調用時若上一級沒有傳遞值則預設為undefined
    new Promise((res, rej)=> {
        res('a');    
    }).then(val=> {
        return 'b'
    }).then(val=> {
        console.log(val);  // 'b'
    }).then((val)=> {
        console.log(val);  // 'undefined'
    });
  • 若上一級的then中傳遞的並非函數,則忽略該級
    new Promise((res, rej)=> {
        res('a');    
    }).then(val=> {
        return 'b';
    }).then(val=> {
        console.log(val);  // 'b'
        return 'c';
    }).then({  // 並非函數
        name: 'lan'
    }).then((val)=> {
        console.log(val);   // 'c'
    });

2.2 參數傳遞例題

let doSomething = function() {
    return new Promise((resolve, reject) => {
        resolve('返回值');
    });
};

let doSomethingElse = function() {
    return '新的值';
}

doSomething().then(function () {
    return doSomethingElse();
}).then(resp => {
    console.warn(resp);
    console.warn('1 =========<');
});

doSomething().then(function () {
    doSomethingElse();
}).then(resp => {
    console.warn(resp);
    console.warn('2 =========<');
});

doSomething().then(doSomethingElse()).then(resp => {
    console.warn(resp);
    console.warn('3 =========<');
});

doSomething().then(doSomethingElse).then(resp => {
    console.warn(resp);
    console.warn('4 =========<');
});

結合上面的講解想一想會輸出什麼?(答案及解析

3. Promise.prototype.then

當Promise中的狀態(pending ---> resolved or rejected)發生變化時才會執行then方法。

  • 調用then返回的依舊是一個Promise實例 ( 所以才可以鏈式調用... )
new Promise((res, rej)=> {
    res('a');
}).then(val=> {
    return 'b';
});

// 等同於
new Promise((res, rej)=> {
    res('a');
}).then(val=> {
    return new Promise((res, rej)=> {
        res('b');
    });
});
  • then中的回調總會非同步執行
new Promise((res, rej)=> {
    console.log('a');
    res('');
}).then(()=> {
    console.log('b');
});
console.log('c');
// a c b
  • 如果你不在Promise的參數函數中調用resolve或者reject那麼then方法永遠不會被觸發
new Promise((res, rej)=> {
    console.log('a'); 
}).then(()=> {
    console.log('b');
});
console.log('c'); 
// a c

4. Promise的靜態方法

Promise還有四個靜態方法,分別是resolve、reject、all、race,下麵我們一一介紹一下。

4.1 Promise.resolve()

除了通過new Promise()的方式,我們還有兩種創建Promise對象的方法,Promise.resolve()相當於創建了一個立即resolve的對象。如下兩段代碼作用相同:

Promise.resolve('a');

new Promise((res, rej)=> {
    res('a');
});

當然根據傳入的參數不同,Promise.resolve()也會做出不同的操作。

  • 參數是一個 Promise 實例

如果參數是 Promise 實例,那麼Promise.resolve將不做任何修改、原封不動地返回這個實例。

  • 參數是一個thenable對象

thenable對象指的是具有then方法的對象,比如下麵這個對象。

let thenable = {
  then: function(resolve, reject) {
    resolve(42);
  }
};

Promise.resolve方法會將這個對象轉為 Promise對象,然後就立即執行thenable對象的then方法。

  • 參數不是具有then方法的對象,或根本就不是對象

如果參數是一個原始值,或者是一個不具有then方法的對象,則Promise.resolve方法返回一個新的 Promise 對象,狀態為resolved。

  • 不帶有任何參數

Promise.resolve方法允許調用時不帶參數,直接返回一個resolved狀態的 Promise 對象。

值得註意的一點是該靜態方法是在本次事件輪詢結束前調用,而不是在下一次事件輪詢開始時調用。關於事件輪詢可以看這裡——>JavaScript 運行機制詳解:再談Event Loop

4.2 Promise.reject()

和Promise.resolve()類似,只不過一個是觸發成功的回調,一個是觸發失敗的回調

4.3 Promise.all()

Promise的all方法提供了並行執行非同步操作的能力,並且在所有非同步操作執行完後才執行回調。

function asyncFun1() {
    return new Promise((res, rej)=> {
        setTimeout(()=> { 
            res('a');
        }, 1000);
    }); 
}
function asyncFun2() {
    return new Promise((res, rej)=> {
        setTimeout(()=> { 
            res('b');
        }, 1000);
    }); 
}
function asyncFun3() {
    return new Promise((res, rej)=> {
        setTimeout(()=> { 
            res('c');
        }, 1000);
    }); 
}
Promise.all([asyncFun1(), asyncFun2(), asyncFun3()]).then((val)=> {
    console.log(val);
});
Promise.all([asyncFun1(), asyncFun2(), asyncFun3()]).then((val)=> {
    console.log(val);  // ['a', 'b', 'c']
});

用Promise.all來執行,all接收一個數組參數,裡面的值最終都算返回Promise對象。這樣,三個非同步操作的並行執行的,等到它們都執行完後才會進到then裡面。有了all,你就可以並行執行多個非同步操作,並且在一個回調中處理所有的返回數據。

適用場景:打開網頁時,預先載入需要用到的各種資源如圖片、flash以及各種靜態文件。所有的都載入完後,我們再進行頁面的初始化。

4.4 Promise.race()

race()和all相反,all()是數組中所有Promise都執行完畢就執行then,而race()是一旦有一個Promise執行完畢就會執行then(),用上面的三個Promise返回值函數舉例

Promise.race([asyncFun1(), asyncFun2(), asyncFun3()]).then((val)=> {
    console.log(val);  // a
});

5. 鏈式調用經典例題

看了這麼多關於Promise的知識,我們來做一道題鞏固一下。

寫一個類Man實現以下鏈式調用

調用方式:
new Man('lan').sleep(3).eat('apple').sleep(5).eat('banana');
列印:
'hello, lan' -(等待3s)--> 'lan eat apple' -(等待5s)--> 'lan eat banana'

思路:

  • 在原型方法中返回this達到鏈式調用的目的
  • 等待3s執行的效果可以用Promise & then實現

具體實現如下:

class Man {
    constructor(name) {
        this.name = name;
        this.sayName();
        this.rope = Promise.resolve();  // 定義全局Promise作鏈式調用
    }
    sayName() {
        console.log(`hello, ${this.name}`);
    }
    sleep(time) {
        this.rope = this.rope.then(()=> {
            return new Promise((res, rej)=> {
                setTimeout(()=> {
                    res();
                }, time*1000);
            });
        });
        return this;
    }
    eat(food) {
        this.rope = this.rope.then(()=> {
            console.log(`${this.name} eat ${food}`); 
        });

        return this;
    }
}

new Man('lan').sleep(3).eat('apple').sleep(5).eat('banana');

ok!不知道你有沒有看懂呢?如果能完全理解代碼那你的Promise可以通關了,順便來個小思考,下麵這種寫法可以嗎?和上面相比有什麼區別?:

class Man1345 {
    constructor(name) {
        this.name = name;
        this.sayName(); 
    }
    sayName() {
        console.log(`hello, ${this.name}`);
    }
    sleep(time) { 
        this.rope = new Promise((res, rej)=> {
                setTimeout(()=> {
                    res();
                }, time*1000);
            }); 
        return this;
    }
    eat(food) {
        this.rope = this.rope.then(()=> { 
            console.log(`${this.name} eat ${food}`);  
        });

        return this;
    }
}

new Man('lan').sleep(3).eat('apple').sleep(5).eat('banana');

簡單的說,第二段代碼的執行結果是

'hello, lan' -(等待3s)--> 'lan eat apple' ---> 'lan eat banana'

為什麼會出現這種差別? 因為第二段代碼每一次調用sleep都會new一個新的Promise對象,調用了兩次sleep就new了兩個Promise對象。這兩個對象是非同步並行執行,會造成兩句eat同時顯示。

和以下情況類似

var time1 = setTimeout(()=> {
    console.log('a');
}, 1000)
var time2 = setTimeout(()=> {
    console.log('b');
}, 1000)
// 同時輸出 a b

抽象一點的講解是:

// 第一段正確的代碼的執行為
var p1 = new Promise().then('停頓3s').then('列印食物').then('停頓5s').then('列印食物');

// 第二段代碼的執行行為,p1、p2非同步並行執行
var p1 = new Promise().then('停頓3s').then('列印食物');
var p2 = new Promise().then('停頓5s').then('列印食物');
總結

Promise的經常用到的地方:

  • 擺脫回調地獄
  • 多個非同步任務同步

Promise是我們的好幫手,不過還有另一種方法也可以做到,那就是async&await,可以多多瞭解一下。

參考資料

ECMAScript 6 入門

通俗淺顯的理解Promise中的then

大白話講解promise


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

-Advertisement-
Play Games
更多相關文章
  • 去年三月底入職上海的一家互聯網公司,由於項目使用的是MongoDB資料庫所以有機會接觸了MongoDB。在項目的開發過程中使用系統原有的一些方法查詢MongoDB感覺很費力,用起來也不爽,所以私下裡就自己學了一些C#查詢MongoDB的方法。 先說一些MongoDB的內嵌數組查詢,公司原有的方法是使 ...
  • 據我觀察,中國的開發者創造了一種獨特的SQL發音:/'sɜːkl/,既好聽,又好讀,挺好的。但是今年我開始做資料庫相關的工作,作為一個專業人士,決定對SQL發音進行一些考證。 直接說結論吧,很多人沿用了/ˈsiːkwəl/這個讀音,因為這門語言以前叫做“SEQUEL”。但更官方一些的讀音應該是ISO ...
  • 圓角設置可以指定左上、左下、右上、右下角;單個指定或多個指定。 ...
  • 概述 在iOS 4.0之後,block橫空出世,它本身封裝了一段代碼並將這段代碼當做變數,通過block()的方式進行回調。這不免讓我們想到在C函數中,我們可以定義一個指向函數的指針並且調用。 。 Block基本使用 Block的類型 block也是一種數據類型,Block的類型是什麼呢。 就是Bl ...
  • 一、模態框 實現圖片點擊後出現彈窗,彈窗裡帶點擊的圖片大圖的效果。 分類: 1.模態對話框 模態對話框(Modal Dialogue Box,又叫做模式對話框),是指在用戶想要對對話框以外的應用程式進行操作時,必須首先對該對話框進行響應。如單擊【確定】或【取消】按鈕等將該對話框關閉。否則無法進行其他 ...
  • 一、如何實現滾動到一定位置將內容固定在頁面頂部 window.onscroll=function(){ //滾動的距離,距離頂部的距離 var topScroll =document.body.scrollTop||document.documentElement.scrollTop; //獲取到導 ...
  • n HTML 註釋 <--! 註釋內容 --> 註意:註釋內容不會顯示,註釋是為了將來維護方面。 n 圖片熱點(圖像地圖) 圖像熱點:給一張圖片加多個鏈接,預設情況下,一張圖片只能加一個鏈接。 1、標記結構: <img src=”images/01.jpg” usemap=”#Map” > <map ...
  • offsetWidth / offsetHeight offsetWidth HTMLElement.offsetWidth 是一個只讀屬性,返回一個元素的佈局寬度。一個典型的(各瀏覽器的offsetWidth可能有所不同)offsetWidth是測量包含元素的邊框(border)、水平線上的內邊距 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...