細說Promise

来源:http://www.cnblogs.com/giggle/archive/2016/06/23/5575157.html
-Advertisement-
Play Games

常用的非同步方式有三種:回調函數、事件監聽以及發佈訂閱,當非同步多且依賴關係很嚴重時,常用的這三種非同步方式不是很完美,ES6中Promise的出現完美地解決了這一弊端,成為非同步調用最佳的方式。該隨筆分為5部分:1、常用三種非同步方式介紹;2、Promise概述;3、Promise的使用;4、模擬Promi... ...


一、前言

JavaScript是單線程的,固,一次只能執行一個任務,當有一個任務耗時很長時,後面的任務就必須等待。那麼,有什麼辦法,可以解決這類問題呢?(拋開WebWorker不談),那就是讓代碼非同步執行嘛。什麼意思,如Ajax非同步請求時,就是通過不斷監聽readyState的值,以確定執行指定的回調函數。

通常的非同步執行有三種,回調函數、事件監聽以及發佈訂閱,其中事件監聽和發佈訂閱其實差不多,只是後者更加健壯一些。

如回調函數,回調函數是應用在非同步執行中最簡單的編程思想。如下:

function async(item,callback){
    console.log(item);
    setTimeout(function(){
        callback(item+1);
    },1000);    
}

在上述列子中,執行async函數時,完成列印操作,併在1秒後執行callback回調函數(但不一定是1秒,詳情見”setTimeout那些事兒”)。

非同步的主要目的就是處理非阻塞,提升性能。想象一下,如果某個操作需要經過多個async函數操作呢,如下:

async(1, function(item){
    async(item, function(item){
        async(item, function(item){
            console.log('To be continued..');
        });
    });
});

是不是有點不易閱讀了?

再比如,為了讓上述代碼更加健壯,我們可以加入異常捕獲。在非同步的方式下,異常處理分佈在不同的回調函數中,我們無法在調用的時候通過try…catch的方式來處理異常, 所以很難做到有效,清楚。

哎喲喂,那可怎麼辦呢?

噔噔噔噔,噔噔噔噔—Promise閃亮登場。

倘若用ES6的Promise優化上述代碼,可得:

function opration(item){
    var p = new Promise(function(resolve, reject){
        setTimeout(function(){
            resolve(item+1);
        },1000);
    });
    console.log(item);
    return p;
}
function failed(e){
    console.log(e);
}
Promise.resolve(1).then(opration).then(opration).then(opration).catch(failed);

Promise 優化後的代碼,優點顯而易見,讓回調函數變成了鏈式調用,避免了層層嵌套,使程式流程變得清晰明朗,併為一個或者多個回調函數拋出的錯誤通過catch方法進行統一處理。

哎呦,不錯嘛,那這個ES6中的Promise到底是何方聖神,具體使用法則是什麼呢?我們就一起來探究探究。

二、Promise概述

Promise是非同步編程的一種解決方案,比傳統的解決方案(回調和事件)更合理和更強大。它由社區最早提出和實現,ES6將其寫進了語言標準,統一了用法,原生提供了Promise對象。

Promise對象有且只有三種狀態:

  1、 pending:非同步操作未完成。

  2、 resolved:非同步操作已完成。

  3、 rejected:非同步操作失敗。

又,這三種狀態的變化只有兩種模式,並且一旦狀態改變,就不會再變:

  1、非同步操作從pending到resolved;

  2、非同步操作從pending到rejected;

好了,既然它是屬於ES6規範,我們再通過chrome,直接列印出Promise,看看這玩意:

 

恩,一目瞭然,Promise為構造函數,歐克,這樣通過它,我們就可以實例化自己的Promise對象了,並加以利用。

三、Promise入門指南

Promise既然是一個構造函數,那麼我們就先new一個看看。如下:

var p = new Promise();

並執行上述代碼,chrome截圖如下:

怎麼報錯了呢?

哦,對了,Promise構造函數,需要一個函數作為其參數哦,並且作為參數的函數中,有兩個參數,第一個參數的作用為,當非同步操作從pending到resolved時,供其調用;第二個參數的作用為,當非同步操作從pending到rejected時,供其調用。

例如,我將匿名函數的第一個參數取名為resolve;第二個參數取名為reject。Demo如下:

var p = new Promise(function(resolve, reject){
    console.log('new一個Promise對象');
    setTimeout(function(){
        resolve('Monkey');
    },1000);
});

並執行上述代碼,chrome截圖如下:

特別提醒:當傳入匿名函數作為構造函數Promise的參數時,我們在new的時候,匿名函數就已經執行了,如上圖。

咦,上述代碼中,我們在匿名函數中,通過setTimeout定時器,在1秒後,還調用了resolve呢,怎麼沒有報undefined或者錯誤呢?!

這就是Promise強大之處的一點。正因為這樣,我們就可以將非同步操作改寫成優雅的鏈式調用。怎麼調用呢?

還記得,我們在“Promise概述”一小節中,通過chrome列印Promise,用紅線框中的區域麽?其中,Promise原型中有一then方法(Promise.prototype.then),通過這個then方法,就可以了。如下:

p.then(function(value){
    console.log(value);
});

其中,then方法有兩個匿名函數作為其參數,與Promise的resolve和reject參數一一對應。執行代碼,結果如下:

好了,當then執行完後,如果我們想繼續在其之後看,使用then方法鏈式調用,有兩種情況,一種是直接返回非Promise對象的結果;另一種是返回Promise對象的結果。

1、返回非Promise對象的結果:緊跟著的then方法,resolve立刻執行。並可使用前一個then方法返回的結果。如下:

p.then(function(value){
    console.log(value);
    //返回非Promise對象,如我的對象
    return {
        name: 'Dorie',
        age: 18
    };
}).then(function(obj){
    console.log(obj.name);
});

執行上述完整代碼,chrome截圖如下:

 

2、返回Promise對象的結果:緊跟著的then方法,與new Promise後的then方法一樣,需等待前面的非同步執行完後,resolve方可被執行。如下:

p.then(function(value){
    var p = new Promise(function(resolve, reject){
        setTimeout(function(){
            var message = value + ' V Dorie'
            resolve(message);
        },1000);
    });
    console.log(value);
    //返回一個Promise對象
    return p;
}).then(function(value){
    console.log(value);
});

執行上述完整代碼,chrome截圖如下:

 

那麼,當創建、執行Promise方法中有異常報錯,如何捕獲呢?

Promise.prototype.catch原型方法,就是為其而設定的。它具有冒泡的特性,比如當創建Promise實例時,就出錯了,錯誤消息就會通過鏈式調用的這條鏈,一直追溯到catch方法,如果找到盡頭都沒有,就報錯,並且再找到catch之前的所有then方法都不能執行了。Demo如下(代碼太長,請自行展開):

<!DOCTYPE html>
    <head>
        <title>test</title>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    </head>
    <body>
        <script>
            var p = new Promise(function(resolve, reject){
                //M未定義
                console.log(M);
                setTimeout(function(){
                    resolve('Monkey');
                },1000);
            });
            p.then(function(value){
                var p = new Promise(function(resolve, reject){
                    setTimeout(function(){
                        var message = value + ' V Dorie'
                        resolve(message);
                    },1000);
                });
                console.log(value);
                //返回一個Promise對象
                return p;
            }).then(function(value){
                console.log(value);
                return 'next is catch';
            }).catch(function(e){
                console.log(e);
            }).then(function(value){
                console.log('execute,but value is ' + value);
            });
        </script>
    </body>
</html>
View Code

執行上述代碼,chrome截圖如下:

 

好了,到這裡,我們已經瞭解了最常用的Promise.prototype.then和Promise.prototype.catch這兩個原型方法。另外,像Promise構造函數還有屬於自身的方法,如all、rece、resolve、reject等,詳情請點擊這裡(here)。

通過一路上對Promise的講述,我們也有了一定的認識,其實Promise並沒有想象中的那麼難以理解嘛。懂得Promise概念後,其實我們自己也可以實現一個簡易版的Promise。下麵就一同嘗試實現一個唄。

四、模擬Promise

假設:有三個非同步操作方法f1,f2,f3,且f2依賴於f1,f3依賴於f2。如果,我們採用ES6中Promise鏈式調用的思想,我們可以將程式編寫成這樣:

f1().then(f2).then(f3);

那麼,通過上面這一系列鏈式調用,怎樣才能達到與ES6中Promise相似的功能呢?

初步想法:首先將上述鏈式調用的f2、f3保存到f1中,當f1中的非同步執行完後,再調用執行f2,並將f1中的f3保存到f2中,最後,等f2中的非同步執行完畢後,調用執行f3。詳細構思圖,如下:

 

從上圖可知,由於f1、f2 、f3是可變得,所以存儲數組隊列thens,可放入,我們即將創建的模擬Promise構造函數中。具體實現代碼如下:

//模擬Promise
function Promise(){
    this.thens = [];
};
Promise.prototype = {
    constructor: Promise,
    then: function(callback){
        this.thens.push(callback);
        return this;        
    }
};

並且,需要一個Promise.prototype.resolve原型方法,來實現:當f1非同步執行完後,執行緊接著f1後then中的f2方法,並將後續then中方法,嫁接到f2中,如f3。具體實現代碼如下:

//模擬Promise,增加resolve原型方法
function Promise(){
    this.thens = [];
};
Promise.prototype = {
    constructor: Promise,
    then: function(callback){
        this.thens.push(callback);
        return this;        
    },
    resolve: function(){
        var t = this.thens.shift(), 
            p;
        if(t){
            p = t.apply(null,arguments);
            if(p instanceof Promise){
                p.thens = this.thens;
            }
        }
    }
};

測試代碼(代碼太長,自行打開並運行)。

function f1() {
    var promise = new Promise();
    setTimeout(function () {
       
        console.log(1);
        promise.resolve();
    }, 1500)

    return promise;
}

function f2() {
    var promise = new Promise();
    setTimeout(function () {
        console.log(2);
        promise.resolve();
    }, 1500);
    return promise;
}

function f3() {
    var promise = new Promise();
    setTimeout(function () {

        console.log(3);
        promise.resolve();
    }, 1500)

    return promise;
}
f1().then(f2).then(f3);
測試代碼

仔細品味,上述實現的Promise.prototype.resolve方法還不夠完美,因為它只能夠滿足於f1、f2、f3等方法都是使用模擬的Promise非同步執行的情況。而,當其中有不是返回的Promise對象的呢,而是返回一個數字,亦或是什麼也不返回,該怎麼辦?所以,針對以上提出的種種可能,再次改進resolve。改善代碼如下:

//模擬Promise,改善resolve原型方法
var Promise = function () {
    this.thens = [];
};
Promise.prototype = {
    constructor: Promise,
    then: function(callback){
        this.thens.push(callback);
        return this;        
    },
    resolve: function () {
        var t,p;
        t = this.thens.shift();
        t && (p = t.apply(null, arguments));
        while(t && !(p instanceof Promise)){
            t = this.thens.shift();
            t && (p = t.call(null, p));    
        }
        if(this.thens.length){
            p.thens = this.thens;
        };
    }
}

測試代碼(代碼太長,自行打開並運行)。

function f1() {
    var promise = new Promise();
    setTimeout(function () {
       
        console.log(1);
        promise.resolve();
    }, 1500)

    return promise;
}

function f2() {
    var promise = new Promise();
    setTimeout(function () {
        console.log(2);
        promise.resolve();
    }, 1500);
    return promise;
}

function f3() {
    var promise = new Promise();
    setTimeout(function () {

        console.log(3);
        promise.resolve();
    }, 1500)

    return promise;
}

function f4() {
    console.log(4);
    return 11;
}

function f5(x) {
    console.log(x+1);
}

function f6() {
    var promise = new Promise();
    setTimeout(function () {

        console.log(6);
        promise.resolve();
    }, 1500)

    return promise;
}

function f7() {
    console.log(7);
}

var that = f1().then(f2).then(f3).then(f4).then(f5).then(f6).then(f7);
測試代碼

好了,初步模擬的Promise就OK啦。

吼吼,對於Promise,我們這一路走來,發現原來也不過如此呢。

五、拓展閱讀

[1]、再談Event Loop

[2]、MDN

[3]、大白話講解Promise(一)

[4]、當耐特

[5]、Promise & Deferred objects in JavaScript Pt.1


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

-Advertisement-
Play Games
更多相關文章
  • **MVP模式結構** - Model: 業務邏輯和實體模型 - View:用戶交互和視圖顯示,在android中對應activity - Presenter: 負責完成View於Model間的邏輯和交互 ...
  • 最近一同事在學習AngularJS,在路由與模板的學習過程中遇到了一些問題,於是今天給她寫了個例子,順便分享出來給那些正在學習AngularJS的小伙伴們。 話說這AngularJs 開發項目非常的爽,其中爽就爽在它的開發模式,使得代碼更加的清晰、更加具有可讀性、更簡潔、更具有維護性。但是在使用An ...
  • 一直對Javascript中的this都有一種似是而非的感覺,今天突然感覺豁然開朗,特此記錄一下。 咱們先看個慄子: 咋一看這段代碼沒有什麼問題,但是由於對於this的錯誤理解最終導致錯誤的結果。我們在元素car_key上面綁定了click事件,認為在car的類中嵌套綁定click事件就可以讓這個d ...
  • 在img標簽裡面只設置寬,不設置高,圖片就會等比例縮放。 ...
  • js對象的核心是一個字元串屬性名與屬性值的映射表。使用對象實現字典易如反掌,字典是可變長的字元串與值的映射集合。 for...in js提供了枚舉一個對象屬性名的利器--for...in迴圈。var dict={zhangsan:34,lisi:24,wangwu:62}; var people=[... ...
  • jQuery.AutoComplete是一個基於jQuery的自動補全插件。藉助於jQuery優秀的跨瀏覽器特性,可以相容Chrome/IE/Firefox/Opera/Safari等多種瀏覽器。 特性一覽: 支持補全列表的寬度設定。 支持補全列表的最大高度設定。 支持補全列表的行數限制。 支持補全 ...
  • 0-判斷變數、參數是否初始化 if(x){} //變數被初始化了或者變數不為空或者變數不為零 1-聲明函數不需要聲明返回值、參數類型,句尾甚至都不需要';' function sum(i1,i2){return i1+i2} 2-直接聲明匿名函數立即使用 var f=function(i1,i2){ ...
  • Adobe Dreamweaver雖然非常好用,但它並不是唯一一個能夠設計、開發、發佈精彩網站的Web開發集成環境。我們的開源世界里有很多非常棒的可以完全替代Dreamweaver的各種功能的優秀Web開發工具,更重要的,是免費的。如果你正在尋找Dreamweaver的替代品,下麵這8款軟體你應該優 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...