Promise是抽象異步處理對象以及對其進行各類操做的組件,其實Promise就是一個對象,用來傳遞異步操做的消息,它不是某門語言特有的屬性,ES6 將其寫進了語言標準,統一了用法,原生提供了Promise對象,Promise對象有如下兩個特色:node
1.對象的狀態不受外界影響
2.一旦狀態改變,就不會再變,任什麼時候候均可以獲得這個結果
git
Promise也如下缺點:es6
1.沒法取消Promise,一旦新建它就會當即執行,沒法中途取消。
2.若是不設置回調函數,Promise內部拋出的錯誤,不會反應到外部。
3.當處於Pending狀態時,沒法得知目前進展到哪個階段(剛剛開始仍是即將完成)。
github
關於Promise的詳細介紹和用法,能夠參考JavaScript Promise迷你書npm
2.爲何要在js中使用Promise
ES6新增了Promise這個特性的意義在於,以往在js中處理異步操做一般是使用回調函數和事件,而有了Promise對象,就能夠將異步操做以同步操做的流程表達出來,避免了層層嵌套的回調函數。此外,Promise對象提供統一的接口,使得控制異步操做更加容易。拿node.js讀取文件舉例子,基於JavaScript的異步處理,以往都是想下面這樣利用回調函數:segmentfault
var fs = require('fs');
fs.readFile('demo.txt', 'utf8', function (err, data) {
if (err) throw err;
console.log(data);
});
複製代碼
而使用Promise能夠這樣寫:數組
var fs = require('fs');
function readFile(fileName) {
return new Promise(function(resolve, reject) {
fs.readFile(fileName, function (err, data) {
if (err) {
reject(err);
} else {
resolve(data);
}
});
});
}
readFile(('demo.txt').then(
function(data) {
console.log(data);
},
function(err) {
throw err;
}
);
複製代碼
這樣的結構就比較清晰了,有同窗看到這要問了,要是有多重嵌套怎麼辦,來看下面這個例子,假如咱們有多個延時任務要處理,在js中便使用setTimeout來實現,在以往就是js中每每是這樣寫:promise
var taskFun = function() {
setTimeout(function() {
// do timeoutTask1
console.log("do timeoutTask1");
setTimeout(function() {
// do timeoutTask2
console.log("do timeoutTask2");
setTimeout(function() {
// dotimeoutTask3
console.log("do timeoutTask3");
}, 3000);
}, 1000);
}, 2000);
}
taskFun();
複製代碼
這樣寫嵌套了多層回調結構,若是業務邏輯再複雜一點,就會進入到所謂的回調地獄,那麼若是用Promise能夠這樣來寫:瀏覽器
new Promise(function(resolve, reject) {
console.log("start timeoutTask1");
setTimeout(resolve, 3000);
}).then(function() {
// do timeoutTask1
console.log("do timeoutTask1");
return new Promise(function(resolve, reject) {
console.log("start timeoutTask2");
setTimeout(resolve, 1000);
});
}).then(function() {
// do timeoutTask1
console.log("do timeoutTask2");
return new Promise(function(resolve, reject) {
console.log("start timeoutTask3");
setTimeout(resolve, 2000);
});
}).then(function() {
// do timeoutTask1
console.log("do timeoutTask3");
});
複製代碼
咱們還能夠用Promise這樣寫,把每一個任務提煉成單獨函數,讓代碼看起來更加優雅直觀:緩存
function timeoutTask1() {
return new Promise(function(resolve, reject) {
console.log("start timeoutTask1");
setTimeout(resolve, 3000);
});
}
function timeoutTask2() {
return new Promise(function(resolve, reject) {
console.log("start timeoutTask2");
setTimeout(resolve, 1000);
});
}
function timeoutTask3() {
return new Promise(function(resolve, reject) {
console.log("start timeoutTask3");
setTimeout(resolve, 2000);
});
}
timeoutTask1()
.then(function() {
// do timeoutTask1
console.log("do timeoutTask1");
})
.then(timeoutTask2)
.then(function() {
// do timeoutTask2
console.log("do timeoutTask2");
})
.then(timeoutTask3)
.then(function() {
// do timeoutTask2
console.log("do timeoutTask3");
});
複製代碼
執行的順序爲:
Promise/A+是Promise的一個主流規範,瀏覽器,node和JS庫依據此規範來實現相應的功能,以此規範來實現一個Promise也能夠叫作實現一個Promise/A+。具體內容可參考Promise/A+規範
1.類和構造器的構建
Promise 的參數是一個函數 task,把內部定義 resolve 和reject方法做爲參數傳到 task中,調用 task。當異步操做成功後會調用 resolve 方法,而後就會執行 then 中註冊的回調函數,失敗是調用reject方法。
class Promise {
constructor(task) {
let self = this; //緩存this
self.status = 'pending'; //默認狀態爲pending
self.value = undefined; //存放着此promise的結果
self.onResolvedCallbacks = []; //存放着全部成功的回調函數
self.onRejectedCallbacks = []; //存放着全部的失敗的回調函數
// 調用resolve方法能夠把promise狀態變成成功態
function resolve(value) {
if (value instanceof Promise) {
return value.then(resolve, reject)
}
setTimeout(() => { // 異步執行全部的回調函數
// 若是當前狀態是初始態(pending),則轉成成功態
// 此處這個寫判斷的緣由是由於resolved和rejected兩個狀態只能由pending轉化而來,二者不能相互轉化
if (self.status == 'pending') {
self.value = value;
self.status = 'resolved';
self.onResolvedCallbacks.forEach(item => item(self.value));
}
});
}
// 調用reject方法能夠把當前的promise狀態變成失敗態
function reject(value) {
setTimeout(() => {
if (self.status == 'pending') {
self.value = value;
self.status = 'rejected';
self.onRejectedCallbacks.forEach(item => item(value));
}
});
}
// 當即執行傳入的任務
try {
task(resolve, reject);
} catch (e) {
reject(e);
}
}
}
複製代碼
代碼思路與要點:
- self = this, 不用擔憂this指向忽然改變問題。
- 每一個 Promise 存在三個互斥狀態:pending、fulfilled、rejected。
- Promise 對象的狀態改變,只有兩種可能:從 pending 變爲 fulfilled 和從 pending 變爲 rejected。只要這兩種狀況發生,狀態就凝固了,不會再變了,會一直保持這個結果。就算改變已經發生了,你再對 Promise 對象添加回調函數,也會當即獲得這個結果。這與事件徹底不一樣,事件的特色是,若是你錯過了它,再去監聽,是得不到結果的。
- 建立 Promise 對象同時,調用其 task, 並傳入 resolve和reject 方法,當 task 的異步操做執行成功後,就會調用 resolve,也就是執行 Promise .onResolvedCallbacks 數組中的回調,執行失敗時同理。
- resolve和reject 方法 接收一個參數value,即異步操做返回的結果,方便傳值。
2.Promise.prototype.then鏈式支持
/**
* onFulfilled成功的回調,onReject失敗的回調
* 原型鏈方法
*/
then(onFulfilled, onRejected) {
let self = this;
// 當調用時沒有寫函數給它一個默認函數值
onFulfilled = isFunction(onFulfilled) ? onFulfilled : value => value;
onRejected = isFunction(onRejected) ? onRejected : value => {
throw value
};
let promise2;
if (self.status == 'resolved') {
promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
try {
let x = onFulfilled(self.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
});
});
}
if (self.status == 'rejected') {
promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
try {
let x = onRejected(self.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
});
});
}
if (self.status == 'pending') {
promise2 = new Promise((resolve, reject) => {
self.onResolvedCallbacks.push(value => {
try {
let x = onFulfilled(value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
});
self.onRejectedCallbacks.push(value => {
try {
let x = onRejected(value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
});
});
}
return promise2;
}
複製代碼
代碼思路與要點:
- 調用 then 方法,將成功回調放入 promise.onResolvedCallbacks 數組;失敗回調放入 promise.onRejectedCallbacks 數組
- 返回一個 Promise 實例 promise2,方便鏈式調用
- then方法中的 return promise2 實現了鏈式調用
- 若是傳入的是一個不包含異步操做的函數,resolve就會先於 then 執行,即 promise.onResolvedCallbacks 是一個空數組,爲了解決這個問題,在 resolve 函數中添加 setTimeout,將 resolve 中執行回調的邏輯放置到 JS 任務隊列末尾;reject函數同理。
3.靜態方法Promise.resolve
static resolve(value) {
return new Promise((resolve, reject) => {
if (typeof value !== null && typeof value === 'object' && isFunction(value.then)) {
value.then();
} else {
resolve(value);
}
})
}
複製代碼
靜態方法Promise.resolve(value)
能夠認爲是 new Promise()
方法的快捷方式。
好比 Promise.resolve(666);
能夠認爲是如下代碼的語法糖。
new Promise(function(resolve){
resolve(666);
});
複製代碼
4.靜態方法Promise.reject
static reject(err) {
return new Promise((resolve, reject) => {
reject(err);
})
}
複製代碼
Promise.reject(err)
是和 Promise.resolve(value)
相似的靜態方法,是 new Promise()
方法的快捷方式。
好比 Promise.reject(new Error("出錯了"))
就是下面代碼的語法糖形式。
new Promise(function(resolve,reject){
reject(new Error("出錯了"));
});
複製代碼
4.靜態方法Promise.all
/**
* all方法,能夠傳入多個promise,所有執行完後會將結果以數組的方式返回,若是有一個失敗就返回失敗
* 靜態方法爲類本身的方法,不在原型鏈上
*/
static all(promises) {
return new Promise((resolve, reject) => {
let result = []; // all方法最終返回的結果
let count = 0; // 完成的數量
for (let i = 0; i < promises.length; i++) {
promises[i].then(data => {
result[i] = data;
if (++count == promises.length) {
resolve(result);
}
}, err => {
reject(err);
});
}
});
}
複製代碼
Promise.all
接收一個 promise對象的數組做爲參數,當這個數組裏的全部promise對象所有變爲resolve或reject狀態的時候,它纔會去調用.then
方法。當所有爲resolve時返回一個所有的resolve執行結果數組,只要有一個不爲resolve狀態,直接返回這個狀態的執行失敗結果。
5.靜態方法Promise.race
/**
* race方法,能夠傳入多個promise,返回的是第一個執行完的resolve的結果,若是有一個失敗就返回失敗
* 靜態方法爲類本身的方法,不在原型鏈上
*/
static race(promises) {
return new Promise((resolve, reject) => {
for (let i = 0; i < promises.length; i++) {
promises[i].then(data => {
resolve(data);
},err => {
reject(err);
});
}
});
}
複製代碼
Promise.race
和Promise.all
相相似,它一樣接收一個數組,race的意思是競賽,顧名思義只要是競賽就有惟一的那個第一名,因此它與all最大的不一樣是隻要該數組中的任意一個 Promise 對象的狀態發生變化(不管是 resolve 仍是 reject)該方法都會返回,因此它只輸出某一個最早執行的狀態結果,而不是像all同樣在所有爲resolve狀態時返回的是一個數組。只需在Promise.all 方法基礎上修改一下就可實現race。
源代碼 以上是對幾個主要方法的介紹,還有些沒有介紹徹底,能夠參考源代碼,源碼文件裏包含了一個測試文件夾以及es5的版本源碼,後續會奉上更爲詳盡的解釋。另外能夠經過安裝一個插件來對實現的promise進行規範測試。
npm(cnpm) i -g promises-aplus-tests
promises-aplus-tests es6Promise.js
複製代碼