摘要: 在好久好久之前,Promise
尚未來到這個世上。那時森林裏的有這樣一羣攻城獅,他們飽受回調地獄(回調金字塔)的摧殘,苦不堪言。直到有一天,一位不肯意留下姓名的特工橫空出世,將他們從回調地獄中解救了出來,代號Promise
。自此,不少人都踏上了尋找Promise
的道路,我亦如此... node
友情提醒: 本文使用ES6實現的Promise
,不會的童鞋們請自行腦補!What?這位同窗你居然不知道ES6,好的,放學了請不要走,咱們單獨交流一下......數組
就拿fs
(node的核心包)來講吧,假如咱們須要同時請求a.txt
和b.txt
中的數據,而後對數據進行操做。這種需求在咱們的開發中也常常遇到哦!promise
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);
})
})
複製代碼
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
此時的心裏應該是這樣的: bash
看到這裏,相信你們都很想知道Promise的核心實現是什麼?接下來,請小夥伴們不要閉眼,看這裏,看這裏!我便說說我是如何在尋找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);
}
}
}
複製代碼
這怎麼僅僅一條線索就寫出來這麼東東呀,真讓人摸不着頭腦!別急,聽我慢慢道來:ui
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
fulfilled
時或者rejected
時,不能再過渡到其餘任何狀態then
函數: 因Promise
是能夠鏈式調用的,說明then
函數是定義在Promise
類的原型Prototype
上的。這樣咱們就成功處理了同步狀況下的
Promise
,是否是以爲本身已經追尋到Promise
的終極力量了呢。(抽根菸,平復下躁動的心情)this
在咱們平時的開發中,每每異步代碼比較多,異步執行須要,然而Promise
的executor
執行器又是同步執行的,它不等咱們怎麼辦呢,好着急有木有。 咱們在上面代碼的基礎上新增以下幾行代碼:url
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
的狀態改變了,咱們再去依次執行保存下來的回調函數。spa
看到這裏,若是以爲本身已經基本掌握Promise
的實現,只能說爾等對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
的核心力量所在。來,讓咱們小小的慶賀一下:
固然,咱們已經初步瞭解了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這個原型的方法你們都已經很熟悉了,我就不過多的囉嗦了。
由於,我實在是編不下去了,我還有更重要的事情要去作:
結語: 花了好久寫的這篇文章,若是這篇文章令你或多或少有些收穫,請不要吝嗇你的讚美(點個贊再走嗎,小哥哥小姐姐),若是有寫的不對的地方,也但願各位大佬能不吝指教,萬分感謝!原創文章,轉載請註明出處!