ES6筆記(7)-- Promise異步編程

 

系列文章 -- ES6筆記系列html

 

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

JS異步編程有利有弊,Promise的出現,改善了這一格局,讓異步編程表現出相似「同步式代碼」的形式,更好地體現了它的價值。git

 

1、基本概念

1. Promises/A+規範

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

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

三個狀態編程

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

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

而且:狀態只能從Pending轉換爲Resolved狀態,或者從Pending轉換爲Rejected狀態,不存在其餘狀態間的轉換promise

Then方法瀏覽器

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

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

 

2. Promise簡單的實現

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

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

2、基本使用

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,三、四、5被reject,整個數組裏已經有多於一個的promise對象被reject,僅僅觸發了catch中的回調,因此log(6)得不到執行

 

Promise.race()方法

與Promise.all()相似,它也接受一個數組對象做爲參數,但其意義不同

只有數組中全部的promise都執行失敗,整個promise纔算失敗,若是數組對象中有個promise執行成功,則整個promise就成功

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

由輸出結果知,1和2被resolve,三、四、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 時候的一些反模式

相關文章
相關標籤/搜索