ES6筆記(7)-- Promise非同步編程

来源:http://www.cnblogs.com/imwtr/archive/2016/09/28/5916793.html
-Advertisement-
Play Games

系列文章 -- ES6筆記系列 很久很久以前,在做Node.js聊天室,使用MongoDB數據服務的時候就遇到了多重回調嵌套導致代碼混亂的問題。 JS非同步編程有利有弊,Promise的出現,改善了這一格局,讓非同步編程表現出類似“同步式代碼”的形式,更好地體現了它的價值。 一、基本概念 1. Prom ...


 

系列文章 -- ES6筆記系列

 

很久很久以前,在做Node.js聊天室,使用MongoDB數據服務的時候就遇到了多重回調嵌套導致代碼混亂的問題。

JS非同步編程有利有弊,Promise的出現,改善了這一格局,讓非同步編程表現出類似“同步式代碼”的形式,更好地體現了它的價值。

 

一、基本概念

1. Promises/A+規範

Promise是一種非同步編程的解決方案,本質來說其實它是一種規範,Promises/A+規範

根據規範的定義,一個Promise對象應該至少有以下的基本特點

三個狀態

Promise有三個狀態:Pending(進行中)Resolved或Fulfilled(已完成)Rejected(已失敗)

其中:Pending為Promise的初始狀態;當Resolved成功時,會調用onFulfilled方法;當Rejected失敗時,會調用onRejected方法

並且:狀態只能從Pending轉換為Resolved狀態,或者從Pending轉換為Rejected狀態,不存在其他狀態間的轉換

Then方法

Promise必須提供一個then方法,用以訪問當前值、最終的返回值以及失敗的原因

最基本的then方法接受兩個函數參數 promise.then(onFulfilled, onReject),對應於狀態變化到Resolved和Rejected時的函數調用

 

2. Promise簡單的實現

基於Promises/A+中規範的要求,可以自行實現一個基本的promise對象

可參考 一步一步實現一個Promise

二、基本使用

1. 使用相關插件

近年來,已經出現了很多Promise非同步編程的插件,我們可以使用這些插件,常見的有:

 

例如使用jQuery新版Ajax模塊內置的Deferred使用到了Promise,我們可以直接這樣調用

// 回調函數的方式
$.get('url', function(rs) {
    rs = JSON.parse(rs);
});

// Promise的形式
$.get('url').success(function(rs) {
    rs = JSON.parse(rs);
})

不過jQuery中的Promise並不是完全按照Primises/A+規範來實現的,所以使用的時候可能會有問題,詳見

2. 原生的Promise支持

ES6原生引入了Promise,它在很多現代瀏覽器上已經得到支持

在不支持原生Promise的環境下,除了可以直接使用一些第三方Promise庫之外,還可以使用這個插件來相容低版本瀏覽器

其實,ES6中的原生Promise實現與RSVP.js有很大的關係,所以相關語法也和它類似

比如在爬蟲開發時,先獲取用戶資料,再獲取該用戶的文章,則可以用Promise,用類似以下的結構實現

function getUser(id) {
    return new Promise(function(resolve, reject) {
        $.get('/user?id=' + id, function(rs) {
            rs = JSON.parse(rs);

            if (rs.status !== 200) {
                reject(rs);
            } else {
                resolve(rs);
            }
        });
    });
}

function getContent(user) {
    return new Promise(function(resolve, reject) {
        $.get('/content', {
            user: user
        }, function(rs) {
            rs = JSON.parse(rs);

            if (rs.status !== 200) {
                reject(rs);
            } else {
                resolve(rs);
            }
        });
    });
}

getUser(11).then(function(rs) {
    return getContent(rs.user);
}).catch(function(rs) {
    throw Error(rs.msg);
}).then(function(rs) {
    console.log(rs.content);
}).catch(function(rs) {
    throw Error(rs.msg);
});

成功調用getUser之後,可以通過return 返回getContent(rs.user)這個promise對象,繼續接下去的執行任務

除了直接返回這個新的promise對象,我們也可以直接返回一個數據,這個數據將會作為下一函數調用時的參數,且看例子:

function step(num) {
    return new Promise(function(resolve, reject) {
        setTimeout(function() {
            if (num > 0) {
                resolve(num);
            } else {
                reject(0);
            }
        })
    });
}

step(-1).then(function(num) {
    console.log('resolve ' + num);
    return -5;
}, function(num) {
    console.log('reject ' + num); // reject 0
    return 5;
}).then(step) // 下一個要執行的任務操作
    .then(function(num) {
    console.log('resolve ' + num); // resolve 5
}, function(num) {
    console.log('reject ' + num);
});

當參數的數值為正數時,則直接resolve返回該數值,如果為負數則reject返回0,初始數值為-1,所以調用了reject

再看另一個例子:

function log(n) {
    return new Promise(function(resolve, reject) {
        setTimeout(function() {

            if (n % 2) {
                resolve('奇數:' + n);
            } else {
                reject('偶數:' + n);
            }
             
        }, 1000);
    });
}

log(1).then(function(data) {
    console.log(data);
    return log(2);
}, function(err) {
    console.log(err);
    return log(3);
}).then(function(data) {
    console.log(data);
}, function(err) {
    console.log(err);
});

以上代碼執行之後

 

下麵來詳細介紹原生Promise的使用方法

new Promise(func)

通過實例化構造函數成一個promise對象,構造函數中有個函數參數,函數參數為(resolve, reject)的形式,供以函數內resolve成功以及reject失敗的調用

 

.then(onFulfilled, onRejected)

then方法,方法帶兩個參數,可選,分別為成功時的回調以及失敗時的回調

如上代碼,log(1)時執行了resolve,log(2)時執行了reject 

 

.catch(onRejected)

catch方法,方法帶一個參數,為失敗時的回調。其實.catch方法就是 .then(undefined, onRejected)的簡化版,通過例子看看它的特點

function log(n) {
    return new Promise(function(resolve, reject) {
        setTimeout(function() {

            if (n % 2) {
                resolve('奇數:' + n);
            } else {
                reject('偶數:' + n);
            }
             
        }, 1000);
    });
}

log(2).then(function(data) {
    console.log(data);
    return log(3);
}).catch(function(err) {
    console.log(err);
});

看這個例子,then中只有一個參數,調用log(2)之後reject執行,到達catch中輸出

再看一個慄子,代碼換成以下兩種,輸出都一樣

log(1).then(function(data) {
    console.log(data);
    return log(2);
}).catch(function(err) {
    console.log(err);
});
log(1).then(function(data) {
    console.log(data);
    return log(2);
}).then(undefined, function(err) {
    console.log(err);
});

如此一來,第一輪log(1)的resolve後,自行調用log(2),從而執行reject,通過catch執行相應的輸出

 

Promise.all()方法

Promise.all()方法接受一個promise的數組對象,只有數組中所有的promise都執行成功,整個promise才算成功,如果數組對象中有個promise執行失敗,則整個promise就失敗

看這個簡單的例子,意圖是調用log(1,2,3,4,5)這個promise完成之後再調用log(6),其中相應值小於3就resolve,反之就reject

 1 function log() {
 2     var promises = [];
 3 
 4     [...arguments].forEach(function(n) {
 5          promises.push(new Promise(function(resolve, reject) {
 6             
 7             var info = '';
 8             setTimeout(function() {
 9                 if (n < 3) {
10                     info = '小於3 resolve:' + n;
11                     console.log(info);
12                     resolve(info);
13                 } else {
14                     info = 'reject:' + n;
15                     console.log(info);
16                     reject(info);
17                 }
18             }, 1000);
19         }));
20     });
21 
22    return Promise.all(promises);
23 }
24 
25 log(1, 2, 3, 4, 5).then(function(data) {
26     console.log(data);
27     return log(6);
28 }).catch(function(err) {
29     console.log(err);
30 });

首先,依次將相應實例化的promise對象存入promises數組,通過Promise.all()調用返回,執行結果為

由輸出結果知,1和2被resolve,3、4、5被reject,整個數組裡已經有多於一個的promise對象被reject,僅僅觸發了catch中的回調,所以log(6)得不到執行

 

Promise.race()方法

與Promise.all()類似,它也接受一個數組對象作為參數,但其意義不一樣

只有數組中所有的promise都執行失敗,整個promise才算失敗,如果數組對象中有個promise執行成功,則整個promise就成功

把上述代碼的all換成race,執行結果為:

由輸出結果知,1和2被resolve,3、4、5被reject,整個數組裡已經有多於一個的promise對象被resolve,觸發了then中成功的回調,log(6)得到調用執行

因為這時還沒有額外的then或catch方法來監視log(6)的狀態,所以僅僅輸出的在log函數中執行的結果

 

Promise.resolve()方法

除了在實例化Promise構造函數內部使用resolve之外,我們還可以直接調用resolve方法

var promise = Promise.resolve('resolve one');
// var promise = Promise.reject('reject one');

promise.then(function(data) {
    console.log(data); // resolve one
}).catch(function(err) {
    console.log(err); 
});

參數除了可以直接指定值之外,還可以是一個Promise實例,具有then方法的對象,或者為空

參數為Promise實例,則將包裝後返回該Promise實例

var promise = Promise.resolve($.get('url'));

前文說到jQuery的Promise實現方式並不是完全按照規範來著,通過Promise.resolve的包裝,可以返回一個規範化的Promise實例

參數為空,則直接返回一個狀態為resolved|fulfilled的Promise對象

setTimeout(function () {
  console.log('three');
}, 0);

Promise.resolve().then(function () {
  console.log('two');
});

console.log('one');

直接resolve的Promise對象是在本輪事件迴圈結束時執行,setTimeout是在下一輪事件迴圈結束時執行,所以輸出為:

參數為一個具有then方法的對象,則自動將這個對象轉換為Promise對象並調用該then方法,如

Promise.resolve({
    then: function(resolve, reject) {
        resolve('resolved');
    }
}).then(function(data) {
  console.log(data); // resolved
}).catch(function(err) {
    console.log(err);
});

 

Promise.reject()方法

除了在實例化Promise構造函數內部使用reject之外,我們還可以直接調用reject方法

類似於Promise.resolve()中參數的多樣化,且看以下幾個慄子:

Promise.resolve({
    then: function(resolve, reject) {
        reject('rejected');
    }
}).then(function(data) {
  console.log(data);
}).catch(function(err) {
    console.log(err); // rejected
});
setTimeout(function () {
  console.log('three');
}, 0);

Promise.reject().catch(function () {
  console.log('two');
});

console.log('one'); 

// one
// two
// three
var promise = Promise.reject($.get('url'));
// var promise = Promise.resolve('resolve one');
var promise = Promise.reject('reject one');

promise.then(function(data) {
    console.log(data);
}).catch(function(err) {
    console.log(err); // reject one
});

 

3. Promise的反模式

關於Promise有很多難點技巧點,比如以下四中調用方式的區別

doSomething().then(function () {
    return doSomethingElse();
});

doSomethin().then(functiuoin () {
    doSomethingElse();
});

doSomething().then(doSomethingElse());

doSomething().then(doSomethingElse);

相關解釋見:談談使用 promise 時候的一些反模式

 


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

-Advertisement-
Play Games
更多相關文章
  • [1]html() [2]text() [3]val() [4]總結 ...
  • 效果圖: 進入工作目錄,運行 react-native init NavigatorProject 創建項目NavigatorProject 延伸:傳參。 以上面的代碼為基礎。 一: 效果圖: 二: 效果圖: ...
  • 1.director.js 支持中文路徑 390行 return mod str ? "([._a-zA-Z0-9-%()]+)" : mod; 修改正則 return mod str ? "([._a-zA-Z0-9-%()]+|[\u4E00-\u9FFF]+)" : mod; 2.ie8bug ...
  • 此博客用來收錄一些常用的正則表達式,希望能不斷修正,不斷完善 ...
  • 宋體 SimSun黑體 SimHei微軟雅黑 Microsoft YaHei微軟正黑體 Microsoft JhengHei新宋體 NSimSun新細明體 PMingLiU細明體 MingLiU標楷體 DFKai-SB仿宋 FangSong楷體 KaiTi仿宋_GB2312 FangSong_GB2 ...
  • 一、浮動導航條 ...
  • ...
  • 使用 @ 定義變數變數可以做運算@color : #000;@width : 1000px;使用 & 表示當前類.box{&:hover{color : #000;}}css 可以嵌套ul{display : block;li{float : left;a{font-size : 18px;}}}繼 ...
一周排行
    -Advertisement-
    Play Games
  • 前言 本文介紹一款使用 C# 與 WPF 開發的音頻播放器,其界面簡潔大方,操作體驗流暢。該播放器支持多種音頻格式(如 MP4、WMA、OGG、FLAC 等),並具備標記、實時歌詞顯示等功能。 另外,還支持換膚及多語言(中英文)切換。核心音頻處理採用 FFmpeg 組件,獲得了廣泛認可,目前 Git ...
  • OAuth2.0授權驗證-gitee授權碼模式 本文主要介紹如何筆者自己是如何使用gitee提供的OAuth2.0協議完成授權驗證並登錄到自己的系統,完整模式如圖 1、創建應用 打開gitee個人中心->第三方應用->創建應用 創建應用後在我的應用界面,查看已創建應用的Client ID和Clien ...
  • 解決了這個問題:《winForm下,fastReport.net 從.net framework 升級到.net5遇到的錯誤“Operation is not supported on this platform.”》 本文內容轉載自:https://www.fcnsoft.com/Home/Sho ...
  • 國內文章 WPF 從裸 Win 32 的 WM_Pointer 消息獲取觸摸點繪製筆跡 https://www.cnblogs.com/lindexi/p/18390983 本文將告訴大家如何在 WPF 裡面,接收裸 Win 32 的 WM_Pointer 消息,從消息裡面獲取觸摸點信息,使用觸摸點 ...
  • 前言 給大家推薦一個專為新零售快消行業打造了一套高效的進銷存管理系統。 系統不僅具備強大的庫存管理功能,還集成了高性能的輕量級 POS 解決方案,確保頁面載入速度極快,提供良好的用戶體驗。 項目介紹 Dorisoy.POS 是一款基於 .NET 7 和 Angular 4 開發的新零售快消進銷存管理 ...
  • ABP CLI常用的代碼分享 一、確保環境配置正確 安裝.NET CLI: ABP CLI是基於.NET Core或.NET 5/6/7等更高版本構建的,因此首先需要在你的開發環境中安裝.NET CLI。這可以通過訪問Microsoft官網下載並安裝相應版本的.NET SDK來實現。 安裝ABP ...
  • 問題 問題是這樣的:第三方的webapi,需要先調用登陸介面獲取Cookie,訪問其它介面時攜帶Cookie信息。 但使用HttpClient類調用登陸介面,返回的Headers中沒有找到Cookie信息。 分析 首先,使用Postman測試該登陸介面,正常返回Cookie信息,說明是HttpCli ...
  • 國內文章 關於.NET在中國為什麼工資低的分析 https://www.cnblogs.com/thinkingmore/p/18406244 .NET在中國開發者的薪資偏低,主要因市場需求、技術棧選擇和企業文化等因素所致。歷史上,.NET曾因微軟的閉源策略發展受限,儘管後來推出了跨平臺的.NET ...
  • 在WPF開發應用中,動畫不僅可以引起用戶的註意與興趣,而且還使軟體更加便於使用。前面幾篇文章講解了畫筆(Brush),形狀(Shape),幾何圖形(Geometry),變換(Transform)等相關內容,今天繼續講解動畫相關內容和知識點,僅供學習分享使用,如有不足之處,還請指正。 ...
  • 什麼是委托? 委托可以說是把一個方法代入另一個方法執行,相當於指向函數的指針;事件就相當於保存委托的數組; 1.實例化委托的方式: 方式1:通過new創建實例: public delegate void ShowDelegate(); 或者 public delegate string ShowDe ...