Promise對象的詳解與實現
參考資料:《ES6標準入門第3版》
目錄
2.5 Promise.resolve() 和 Promise.reject() 把現有對象轉爲Promise對象
2.6 Promise.all() 和 Promise.race()
一 、Promise對象詳解
1. 什麼是Promise?
Promise 是異步編程的一種解決方案,比傳統的解決方案——回調函數和事件更合理和更強大,解決了傳統解決方案處理異步編程出現回調地獄的情況。它由社區最早提出和實現,ES6 將其寫進了語言標準,統一了用法,原生提供了 Promise 對象。
所謂 Promise,簡單說就是一個容器,裏面保存着某個未來纔會結束的事件(通常是一個異步操作)的結果。從語法上說,Promise 是一個對象,從它可以獲取異步操作的消息。Promise 提供統一的 API,各種異步操作都可以用同樣的方法進行處理。
1.1 Promise對象的三種狀態
Promise對象代表着一個異步操作,它有三種狀態:pending(異步操作進行中)、fulfilled(異步操作已完成)、rejected(異步操作已經失敗)
1.2 Promise對象的三個特點
(1)對象的狀態不受外界影響。Promise
對象代表一個異步操作,三種狀態:pending
(進行中)、fulfilled
(已成功)和rejected
(已失敗)。只有異步操作的結果,可以決定當前是哪一種狀態,任何其他操作都無法改變這個狀態。
(2)一旦狀態改變,就不會再變,任何時候都可以得到這個結果。Promise
對象的狀態改變,只有兩種可能:從pending
變爲fulfilled
和從pending
變爲rejected
。只要這兩種情況發生,狀態就凝固了,不會再變了,至始至終會一直保持這個結果。
(3)Promise
也有一些缺點。例如,無法取消Promise
,一旦新建Promise對象它就會立即執行傳入的函數,無法中途取消。
注意,爲了方便理解(與 resolve 函數對應),統一使用 resolved 指代 fullfilled
狀態。
2. 基本用法
ES6 規定,Promise
對象是一個構造函數,用來生成Promise
實例。
下面代碼創造了一個Promise
實例。
注:文件讀寫是一個異步操作。
const fs = require('fs')
var promise = new Promise(function (resolve, reject) {
fs.readFile('./1.txt', 'utf-8', (err, data)=>{
if(err)
// 異步操作失敗:文件讀寫失敗
reject(err)
else
// 異步操作成功:文件讀寫成功
resolve(data)
})
})
2.1 新建Promise傳入的參數
Promise
構造函數接受一個函數作爲參數,該函數的兩個參數分別是resolve
和reject
。它們是兩個函數,由 JavaScript 引擎提供,不用自己部署。
resolve
函數的作用是,將Promise
對象的狀態從“未完成”變爲“成功”(即從 pending 變爲 resolved),在異步操作成功時調用,並將異步操作的結果,作爲參數傳遞出去,參數也可不傳。
reject
函數的作用是,將Promise
對象的狀態從“未完成”變爲“失敗”(即從 pending 變爲 rejected),在異步操作失敗時調用,並將異步操作報出的錯誤,作爲參數傳遞出去,參數也可不傳。
2.2 then方法的作用
Promise
實例生成以後,可以用then
方法分別指定resolved
狀態和rejected
狀態的回調函數,Promise 實例對象的 .then 方法中接收兩個參數,第一個參數是 Promise 對象由 pending 變成 resolved 時調用的回調函數 resolve,第二個參數是 Promise 對象由 pending 變成 rejected 狀態的時候調用的回調函數 reject。
promise.then(function(val){
// promise 執行成功的回調函數的內容
},function(err){
// promise 執行失敗的回調函數的內容
})
2.3 一個完整的例子
其中當前目錄下有一個文件 1.txt 其中的內容是 “111”
const fs = require('fs')
var promise = new Promise(function (resolve, reject) {
console.log("我被立即執行了")
fs.readFile('./1.txt', 'utf-8', (err, data)=>{
if(err)
// 異步操作失敗:文件讀寫失敗
reject(err)
else
// 異步操作成功:文件讀寫成功
resolve(data)
})
})
promise.then(function(val){
console.log(val)
},function(err){
console.log(err)
})
在上述代碼中,創建 promise 實例對象後,就會輸出“我被立即執行了”。
在 promise 對象中文件的讀取是異步操作,把它封裝在 promise 對象當中,then 方法指定的成功或者失敗時會執行的回調函數。
當readFile方法在執行的時候,由於是異步操作,結果不會立即返回,所以容器的狀態是pending。當文件讀取完成後,promise容器的狀態由 pending 變爲 resolved 或者 rejected,此時纔會執行相應的回調函數。
這裏文件讀取成功的結果會輸出 “111”(即執行 resolve 函數),若文件 1.txt 不存在則會報錯,“Error: ENOENT: no such file or directory“(即執行 reject 函數)。
3. Promise 的 then() 方法與鏈式調用
Promise 實例具有then
方法,也就是說,then
方法是定義在原型對象Promise.prototype
上的。它的作用是爲 Promise 實例添加狀態改變時的回調函數。then
方法的第一個參數是resolved
狀態的回調函數,第二個參數(可選)是rejected
狀態的回調函數。
可以在 then 方法中傳入的回調函數返回一個新的 Promise 實例,這樣可以採用鏈式寫法,在 then 方法後可以再調用另外一個 then 方法,同時採用這樣寫法的第二個 Promise 實例會等待第一個 Promise 實例執行完對象內的異步操作執行回調函數結束後才執行,這樣就可以保證兩個或者多個異步操作可以按序執行。
const fs = require('fs')
function readFile(file){
return new Promise(function (resolve, reject) {
fs.readFile(file, 'utf-8', (err, data)=>{
if(err)
reject(err)
else
resolve(data)
})
})
}
readFile("1.txt").then(data => {
console.log(data);
return readFile("2.txt")
}).then(data=>{
console.log(data)
})
// 111
// 222
4. Promise 的 catch() 方法
Promise 對象的 then 方法中可以不指定異步操作發生錯誤的 rejectd 狀態下的回調函數,可以在一系列 Promise 對象鏈式調用的最後使用 catch 方法來捕捉 Promise 對象發生的錯誤。
readFile("1.txt").then(data => {
console.log(data)
return readFile("2.txt")
}).then(data=>{
console.log(data)
return readFile("3.txt")
}).then(data=>{
console.log(data)
}).catch(err=>{
console.log(err)
})
// catch 方法可以捕捉鏈式寫法中所有 promise 對象發生的錯誤
// 一旦在鏈中有 promise 對象發生錯誤,該鏈條後的所有 then 方法不再執行,轉向執行 catch 方法中的回調函數。
Promise 對象的錯誤具有“冒泡”性質,會一直向後傳遞,直到被捕獲爲止。也就是說,錯誤總是會被下一個catch
語句捕獲。
5. Promise.resolve() 和 Promise.reject() 方法
使用Promise原型對象中的 Promise.resolve() 方法可以把現有對象轉爲新的 Promise 對象,狀態爲resolve。
Promise.resolve('foo')
// 等價於
new Promise(resolve => resolve('foo'))
Promise.resolve() 方法的參數分爲以下四種情況。
5.1 參數是一個Promise實例
如果參數是一個 Promise 實例,那麼 Promise.resolve 將不做任何修改,原封不動地返回這個實例。
5.2 參數是一個帶有then方法的對象
例如下面的thenObj:
var thenObj = {
then: function(resolve, reject){
resolve(1);
}
}
此時Promise.resolve() 方法會將這個對象轉爲 Promise 對象,然後立即執行 thenObj 對象的 then 方法。
5.3 參數不是具有then方法的對象或根本不是對象
如果參數是一個原始值,或者是一個不具有then方法的對象,那麼Promise.resolve方法返回一個新的Promise對象,狀態爲Resolved。
var p = Promise.resolve('Hello World');
p.then(function (s){
console.log(s)
});
// 輸出 Hello World
5.4 不帶任何參數
Promise.resolve 方法允許在調用時不帶有參數,而直接返回一個 Resolved 狀態的 Promise 對象。
var p = Promise.resolve()
p.then(function(){
// 要執行的代碼
});
5.5 Promise.reject()
Promise.reject() 方法也會立即返回一個新的 Promise 實例,狀態爲 rejected。
其餘用法與Promise.resolve的四個用法一致。
6. Promise.all() 和 Promise.race()
Promise.all
方法用於將多個 Promise 實例,包裝成一個新的 Promise 實例。
// p1、p1、p3均爲 promise 對象
const p = Promise.all([p1, p2, p3]);
上面代碼中,Promise.all
方法接受一個數組作爲參數,p1
、p2
、p3
都是 Promise 實例,如果不是,就會先調用上面講到的Promise.resolve
方法,將參數轉爲 Promise 實例,再進一步處理。(Promise.all
方法的參數可以不是數組,但必須具有 Iterator 接口,且返回的每個成員都是 Promise 實例。)
p
的狀態由p1
、p2
、p3
決定,分成兩種情況。
(1)只有p1
、p2
、p3
的狀態都變成fulfilled
,p
的狀態纔會變成fulfilled
,此時p1
、p2
、p3
的返回值組成一個數組,傳遞給p
的回調函數。
(2)只要p1
、p2
、p3
之中有一個被rejected
,p
的狀態就變成rejected
,此時第一個被reject
的實例的返回值,會傳遞給p
的回調函數。
Promise.race
方法同樣是將多個 Promise 實例,包裝成一個新的 Promise 實例。
const p = Promise.race([p1, p2, p3]);
上面代碼中,只要p1
、p2
、p3
之中有一個實例率先改變狀態(race競賽),p
的狀態就跟着改變。那個率先改變的 Promise 實例的返回值,就傳遞給p
的回調函數。
Promise.race
方法的參數與Promise.all
方法一樣,如果不是 Promise 實例,就會先調用上面講到的Promise.resolve
方法,將參數轉爲 Promise 實例,再進一步處理。
二、Promise對象的實現
大概理解了Promise 對象的使用方式,可以自己實現一個 Promise 來深入瞭解 Promise 對象的原理。
下面是我認爲寫的很好且容易讀懂的一段代碼,轉載自https://blog.csdn.net/yangbo1993/article/details/79034868
另外一篇寶藏文章:https://www.jianshu.com/p/43de678e918a
/**
* 實現一個Promise
*/
function Promise(task) {
//接收一個處理函數
let that = this;//緩存this
//promise有三種狀態 默認爲pending
that.status = 'pending';
that.onFulfilledFns = [];//所有成功的回調
that.onRejectedFns = [];//所有失敗的回調
that.value = undefined;
function resolve(value) {
//成功函數
if(that.status == 'pending'){
that.status = 'fulfilled';
that.value = value;
//執行所有成功的回調
that.onFulfilledFns.forEach(item=>item(value));
}
};
function reject(reason) {
//失敗函數
if(that.status == 'pending'){
that.status = 'rejected';
that.value = reason;
//執行所有失敗的回到
that.onRejectedFns.forEach(item=>item(reason));
}
};
//立即執行傳入的處理函數
try{
task(resolve,reject);
}catch (err){
reject(err)
}
};
function resolvePromise(promise2,x,resolve,reject) {
let then;
if(promise2 === x){
return reject(new Error('循環引用'));
}
if(x instanceof Promise){
//判斷x的prototype所指向的對象是否存在Promise的原型鏈上
if(x.status= 'pending'){
x.then(function (y) {
//遞歸 調用
resolvePromise(promise2,y,resolve,reject);
},reject)
}else if(x.status == 'fulfilled'){
resolve(x.value);
}else if(x.status == 'rejected'){
reject(x.value);
}
}else if(x != null && typeof x == 'object' || typeof x == 'function'){
try{
then = x.then;
if(typeof then == 'function'){
then.call(x,function (y) {
resolvePromise(promise2,y,resolve,reject);
},function (y) {
reject(y)
});
}
}catch (e){
reject(e);
}
}else{
resolve(x);
}
}
//then方法
Promise.prototype.then = function (onFulfilled, onRejected) {
//假如沒有傳入異步處理程序則直接返回結果
onFulfilled = typeof onFulfilled == 'function'?onFulfilled:function (value) {
return value;
};
onRejected = typeof onRejected == 'function'?onRejected:function (reason) {
return reason;
};
var promise2;//用來實現鏈式調用
let that = this;
if(that.status == 'fulfilled'){
promise2 = new Promise(function (resolve,reject) {
let x = onFulfilled(that.value);
resolvePromise(promise2,x,resolve,reject);
});
}else if(that.status == 'rejected'){
promise2 = new Promise(function (resolve,reject) {
let x = onRejected(that.value);
reject(x);
});
}else if(that.status == 'pending'){
promise2 = new Promise(function (resolve,reject) {
that.onFulfilledFns.push(function(){
let x = onFulfilled(that.value);
resolve(x);
});
that.onRejectedFns.push(function () {
let x = onRejected(that.value);
reject(x);
});
});
}else{
promise2 = new Promise(function (resolve,reject) {
reject('Promise內部狀態錯誤');
});
}
return promise2;
};
Promise.resolve = function (val) {
return new Promise(function (resolve,reject) {
resolve(val);
});
};
Promise.reject = function (val) {
return new Promise(function (resolve,reject) {
reject(val);
});
};
Promise.all = function (arrs) {
//all方法接收一個promise數組,數組中所有異步操作結束後返回一個新的promise
if(typeof arrs == 'object' && arrs.length > 0){
return new Promise(function (resolve,reject) {
let result = [];//新的promise返回結果
let indexNum = 0;//當前完成幾個
let resolved = function (index) {
return function (data) {
result[index] = data;
indexNum++;
if(indexNum == arrs.length){
resolve(result);
}
}
};
for(let i=0;i<arrs.length;i++){
arrs[i].then(resolved(i),function (reason) {
reject(reason);
});
};
});
}else{
return new Promise(function (resolve,reject) {
reject(new Error('all方法傳入參數錯誤'));
});
}
};
Promise.race = function (arrs) {
if(typeof arrs == 'object' && arrs.length > 0){
return new Promise(function (resolve,reject) {
for(let i=0;i<arrs.length;i++){
arrs[i].then(function (data) {
resolve(data);
},function (err) {
reject(err);
});
};
});
}else{
return new Promise(function (resolve,reject) {
reject(new Error('race方法傳入參數錯誤'));
})
};
};
module.exports = Promise;