ES6版Promise實現,給你不一樣的體驗
摘要: 在很久很久以前,Promise
還沒有來到這個世上。那時森林裡的有這樣一群攻城獅,他們飽受回撥地獄(回撥金字塔)的摧殘,苦不堪言。直到有一天,一位不願意留下姓名的特工橫空出世,將他們從回撥地獄中解救了出來,代號Promise
。自此,很多人都踏上了尋找Promise
的道路,我亦如此…
友情提醒: 本文使用ES6實現的Promise
,不會的童鞋們請自行腦補!What?這位同學你竟然不知道ES6,好的,放學了請不要走,我們單獨交流一下……
回撥地獄 VS Promise
就拿fs
(node的核心包)來說吧,假如我們需要同時請求a.txt
和b.txt
中的資料,然後對資料進行操作。這種需求在我們的開發中也經常遇到哦!
- 曾經的回撥地獄
let fs = require(`fs`);
let arr = [];
fs.readFile(`a.txt`,`utf8`,function(err,data){
arr.push(data);
fs.readFile(`b.txt`,`utf8`,function(err,data){
arr.push(data);
// 如果需要把更多的檔案資料,那滋味不敢想象
console.log(arr);
})
})
複製程式碼
- 現在的Promise
let fs = require(`fs`);
function read(url,coding){ // 首先我們對fs.readFile()進行promise封裝
return new Promise((resolve,reject)=>{
fs.readFile(url,coding,function(err,data){
if(err) reject(err);
resolve(data);
})
})
}
Promise.all([read(`a.txt`,`utf8`),read(`b.txt`,`utf8`)]).then(data=>{
// 這裡我們就可以直接操作請求到的兩個檔案的資料了,Promise還很貼心的返回了一個陣列
console.log(data);
})
複製程式碼
相比較之下,Promise
和回撥地獄的戰爭起初就不是一個等級的呀,回撥地獄聽起來強大,但實則一點不經揍啊!Promise
此時的內心應該是這樣的:
Promise之自己實現
看到這裡,相信大家都很想知道Promise的核心實現是什麼?接下來,請小夥伴們不要閉眼,看這裡,看這裡!我便說說我是如何在尋找Promise
的道路上一條道走到黑的。(這標題起的,笑出豬叫聲)
1、Promise 類封裝
起初,我發現Promise是可以被new的,說明Promise 的出身是一個類啊,這可是一條很有價值的線索啊。(大家都知道,還用你說)
class Promise {
constructor(executor) { // executor是new Promise的引數
this.status = `pending`; // 儲存狀態
this.reason = undefined; // 失敗的原因
this.value = undefined; // 成功的結果
let resolve = (value)=> {
if(this.status === `pending`){
this.status = `resolved`;
this.value = value;
}
}
let reject = (reason)=>{
if(this.status === `pending`){
this.status = `rejected`;
this.reason = reason;
}
}
try {
executor(resolve, reject); // 執行器
} catch (e) {
reject(e);
}
}
// 定義例項上的then方法,then呼叫的時候都是非同步呼叫的
then(onFulfilled, onRejected) {
if(this.status === `resolved`){ // status狀態改變時執行onFulFilled
onFulfilled(this.value);
}
if(this.status === `rejected`){ // status狀態改變時執行onFulFilled
onRejected(this.reason);
}
}
}
複製程式碼
這怎麼僅僅一條線索就寫出來這麼東東呀,真讓人摸不著頭腦!別急,聽我慢慢道來:
executor
:執行器,預設是new的時候就自動執行,executor的執行是同步的,為什麼要try一下呢,executor執行時如果throw new Error(`error`)
,直接走rejectresolve, reject
:定義了executor
的resolve
成功的回撥函式和reject
失敗的回撥函式兩個引數reason,value
:分別代表了成功返回的值和失敗的原因status
:儲存了Promise
的三種狀態pending
(等待態),fulfilled
(成功態),rejected
(失敗態)- 當一個
promise
的狀態處於pending
時,它可以過渡到fulfilled
或者ejected
- 當一個promise的狀態處於
fulfilled
時或者rejected
時,不能再過渡到其他任何狀態
- 當一個
then
函式: 因Promise
是可以鏈式呼叫的,說明then
函式是定義在Promise
類的原型Prototype
上的。
這樣我們就成功處理了同步情況下的
Promise
,是不是覺得自己已經追尋到Promise
的終極力量了呢。(抽根菸,平復下躁動的心情)
2、Promise非同步的實現
在我們平時的開發中,往往非同步程式碼比較多,非同步執行需要,然而Promise
的executor
執行器又是同步執行的,它不等我們怎麼辦呢,好著急有木有。
我們在上面程式碼的基礎上新增如下幾行程式碼:
class Promise {
constructor(executor) {
this.onResolvedCallbacks = []; // 儲存成功的回撥
this.onRejectedCallbacks = []; // 儲存失敗的回撥
let resolve = (value)=> {
if(this.status === `pending`){
this.status = `resolved`;
this.value = value;
this.onResolvedCallbacks.forEach(fn=>fn());
}
}
let reject = (reason)=>{
if(this.status === `pending`){
this.status = `rejected`;
this.reason = reason;
this.onRejectedCallbacks.forEach(fn=>fn());
}
}
}
then(onFulfilled, onRejected) {
if(this.status === `pending`){
this.onResolvedCallbacks.push(()=>{
onFulfilled(this.value);
});
this.onRejectedCallbacks.push(()=>{
onRejected(this.reason);
});
});
}
}
}
複製程式碼
當出現非同步程式碼時,status
的狀態還是pending
,我們可以先把then
函式中成功和失敗的回撥儲存下來,等到非同步程式碼執行完成後,status
的狀態改變了,我們再去依次執行儲存下來的回撥函式。
看到這裡,如果覺得自己已經基本掌握Promise
的實現,只能說爾等對Promise
的核心力量一無所知。(別廢話,趕緊寫)好的,各位大佬!
3、Promise之鏈式呼叫的實現
在開始實現之前呢,我們先來看一下如下程式碼:
// 這裡的Promise是ES6封裝好的,並不是我們自己實現的
let promise = new Promise((resolve,reject)=>{
resolve(`123`);
})
let promise2 = promise.then((data)=>{
throw new Error(`error`);
})
promise2.then((data)=>{
console.log(data);
},(err)=>{
console.log(err); // 這裡輸出了error
})
複製程式碼
上面程式碼說明
then
函式執行後返回的promise2
例項並不是promise
例項,說明返回值不是this
,因為promise
不能即呼叫成功後不能再走失敗,所以then
函式執行後返回的promise2
是一個新的promise
例項。(跟jQuery的鏈式呼叫不一樣哦)
Promise
的constructor
的程式碼不需要改變,只需要對then
函式進行再次封裝:
then(onFulfilled, onRejected) {
// onFulfilled和onRejected可能沒傳
onFulfilled = typeof onFulfilled === `function` ? onFulfilled : value=>value;
onRejected = typeof onRejected === `function` ? onRejected : (err)=>{throw err};
let promise2; // 需要每次呼叫then方法時,都需要返回新的promise
promise2 = new Promise((resolve, reject)=>{
if(this.status === `resolved`){
setTimeout(()=>{
try {
let x = onFulfilled(this.value);
// 執行完當前回撥後返回的結果可能還是個promise
resolvePromise(promise2,x,resolve, reject);
} catch (e) {
reject(e);
}
},0)
}
if(this.status === `rejected`){
setTimeout(()=>{
try {
let x = onRejected(this.reason);
resolvePromise(promise2,x,resolve, reject);
} catch (e) {
reject(e);
}
},0)
}
if(this.status === `pending`){
this.onResolvedCallbacks.push(()=>{
setTimeout(()=>{
try {
let x = onFulfilled(this.value);
resolvePromise(promise2,x,resolve, reject);
} catch (e) {
reject(e);
}
},0)
});
this.onRejectedCallbacks.push(()=>{
setTimeout(()=>{
try {
let x = onRejected(this.reason);
resolvePromise(promise2,x,resolve, reject);
} catch (e) {
reject(e);
}
},0)
});
}
})
return promise2;
}
複製程式碼
onFulfilled,onRejected
:當沒有傳的時候,需要做的處理promise2
:then
函式的返回值是一個新的promisesetTimeout
:Promise/A+規範(規範)要求then
函式必須是非同步的,當然原生的Promise實現並不是用的setTimeout,而是一個微任務resolvePromise
:封裝resolvePromise
方法,當then函式中的成功或者失敗函式返回值x可能還是個promise
定義的resolvePromise方法:
let resolvePromise = (promise2,x,resolve, reject)=>{
let called;
// promise2和函式的返回值是同一個
if(promise2 === x){
return reject(new TypeError(`Chaining cycle detected for promise #<Promise>`));
}
if(x!==null && (typeof x === `object` || typeof x === `function`)){
try {
let then = x.then;
if(typeof then === `function`){
then.call(x,(y)=>{
if(called) return;
called = true;
resolvePromise(promise2,y,resolve, reject);// 遞迴處理,直到y是一個普通值
},(err)=>{
if(called) return;
called = true;
reject(err);
})
}else{ // then如果是一個常量
if(called) return;
called = true;
resolve(x);
}
} catch (e) {
if(called) return;
called = true;
reject(e);
}
}else{ // x如果是一個常量
if(called) return;
called = true;
resolve(x);
}
}
複製程式碼
- 四個引數:
promise2
(then函式的返回值,是一個新的promise)
x
(then中成功後者失敗函式的返回值)resolve
(promise2的resolve)reject
(promise2的reject) called
: 加了called
判斷,防止多次呼叫,因為這裡的邏輯不單單是自己的,還有別人的,別人的promise
可能即會呼叫成功也會呼叫失敗let then = x.then
:x
可能還是一個promise
,那麼就讓這個Promise
執行
至此,我們終於追尋到了promise
的核心力量所在。來,讓我們小小的慶賀一下:
3、Promise之類上的方法實現
當然,我們已經初步瞭解了promise
的核心力量,在我們開發的過程中,除了then方法,也會使用它的一些其他常用的方法,就像一位身經百戰的特工,你除了會用刀,還要會用槍不是。我們在Promise類上定義它們:
static resolve(value){
return new Promise((resolve,reject)=>{
resolve(value);
})
}
static reject(reason){
return new Promise((resolve,reject)=>{
reject(reason);
})
}
static all(promises){
return new Promise((resolve,reject)=>{
let arr = [];
let i = 0;
let processData = (index,data)=>{
arr[index] = data;
if(++i === promises.length){
resolve(arr);
}
}
for(let i = 0; i< promises.length; i++){
promises[i].then(data=>{
processData(i,data);
},reject);
}
})
}
static race(promises){
return new Promise((resolve,reject)=>{
for(let i = 0; i< promises.length; i++){
promises[i].then(resolve,reject);
}
})
}
catch(onRejected){
return this.then(null,onRejected);
}
複製程式碼
相信resolve,reject,all,race這四個類上的方法和catch這個原型的方法大家都已經很熟悉了,我就不過多的囉嗦了。
因為,我實在是編不下去了,我還有更重要的事情要去做:
結語: 花了很久寫的這篇文章,如果這篇文章令你或多或少有些收穫,請不要吝嗇你的讚美(點個贊再走嗎,小哥哥小姐姐),如果有寫的不對的地方,也希望各位大佬能不吝指教,萬分感謝!原創文章,轉載請註明出處!