動手搞一個Promise

圖片描述

Javascript語言的執行環境是"單線程"(single thread)。所謂"單線程",就是指一次只能完成一件任務。若是有多個任務,就必須排隊,前面一個任務完成,再執行後面一個任務,以此類推。ajax

這種模式的好處是實現起來比較簡單,執行環境相對單純;壞處是隻要有一個任務耗時很長,後面的任務都必須排隊等着,會拖延整個程序的執行。常見的瀏覽器無響應(假死),每每就是由於某一段Javascript代碼長時間運行(好比死循環),致使整個頁面卡在這個地方,其餘任務沒法執行。編程

爲了解決這個問題,Javascript語言將任務的執行模式分紅兩種:同步(Synchronous)和異步(Asynchronous)。json

異步模式"編程的4種方法:回調函數、事件監聽、發佈/訂閱、Promises對象
還有generator、async/await.數組

本文嘗試說一下對Promise的理解及如何實現。
1.Promise原理promise

promise對象有三種狀態,pending、fulfilled和rejected。promise對象內部保存一個須要執行一段時間的異步操做,當異步操做執行結束後能夠調用resolve或reject方法,來改變promise對象的狀態,狀態一旦改變就不能再變。new一個promise後能夠經過then方法,指定resolved和rejected時的回調函數。下面是咱們平常使用Promise的代碼邏輯。
let p = new Promise((resolve,reject)=>{瀏覽器

$.ajax({
    url: "../www/data.txt",
    dataType: "json",
    success(data){
        resolve(data);
     },
    error(err){
        reject(err);
     }
   }); 
});
p.then(function(data){
    alert("success"+data);
},function(err){
    alert("failed");
})

結合Promise A+規範,咱們就能夠分析一下咱們要實現一個什麼東西:異步

實現一個狀態機,有三個狀態,pending、fulfilled、rejected,狀態之間的轉化只能是pending->fulfilled、pending->rejected,狀態變化不可逆。
實現一個then方法,能夠用來設置成功和失敗的回調
then方法要能被調用屢次,因此then方法須要每次返回一個新的promise對象,這樣才能支持鏈式調用。
構造函數內部要有一個value值,用來保存上次執行的結果值,若是報錯,則保存的是異常信息。
2.實現原理async

2.1實現狀態機函數

那咱們如今就按照上面提到的原理和規範來實現這個Promise構造函數。
class myPromise {this

constructor(executor) {
        this.status = PENDING;
        this.value = '';
        this.Resolve = this.resolve.bind(this);
        this.Reject = this.reject.bind(this);
        this.then= this.then.bind(this);
        executor(this.Resolve, this.Reject);
    }

    resolve(value) {
        if (this.status === PENDING) {
            this.value = value;
            this.status = FULFILLED;
        }
    }

    reject(value) {
        if (this.status === PENDING) {
            this.value = value;
            this.status = REJECTED;
        }
    }

    then(onfulfilled, onrejected) {
        if (this.status === FULFILLED) {
            onfulfilled(this.value);
        }
        if (this.status === REJECTED) {
            onrejected(this.value);
        }
    }
}

const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';

const test = new myPromise((resolve, reject) => {
    resolve(100);
});
test.then((data) => {
    console.log(data);
}, (data) => {
});

由於Promise是一個構造函數,使用ES6的寫法,首先想到的就是有顯式constructor聲明的class。上面就是咱們用class的實現,能夠看到這樣咱們就實現了這個狀態機,有status, value兩個屬性和resolve, reject, then三個函數;同時它有pending, fulfilled和rejected三個狀態,其中pending就能夠切換爲fulfilled或者rejected兩種。

運行一下,輸出了100,可是如今其實不是一個異步處理方案,代碼先運行了resolve(100)而後又運行了then函數,其實對於異步的狀況沒有處理,不信的話就給resolve加一個setTimeout,好了,代碼又沒有輸出了。

2.2 實現異步設置狀態

做爲一個異步處理的函數,在使用的時候,咱們確定是會先設置好不一樣異步返回後的處理邏輯(即then的成功、失敗調用函數),而後安心等待異步執行,最後再異步結束後,系統會自動根據咱們的邏輯選擇調用不一樣回調函數。換句話說,then函數要對status爲pending的狀態進行處理。處理的原理是設置兩個數組,在pending狀態下分別保存成功和失敗回調函數,當狀態改變後,再根據狀態去調用數組中保存的回調函數。

class myPromise {
constructor (executor) {

this.status = PENDING;
this.value = '';
this.onfulfilledArr = [];
this.onrejectedArr = [];
this.resolve = this.resolve.bind(this);
this.reject = this.reject.bind(this);
this.then = this.then.bind(this);
executor(this.resolve, this.reject);

}

resolve (value) {

if (this.status === PENDING) {
  this.value = value;
  this.onfulfilledArr.forEach(item => {
    item(this.value);
  })
  this.status = FULFILLED; 
}

}

reject (value) {

if (this.status === PENDING) {
  this.value = value;
  this.onrejectedArr.forEach(item => {
    item(this.value);
  })
  this.status = REJECTED;
}

}

then (onfulfilled, onrejected) {

if (this.status === FULFILLED) {
  onfulfilled(this.value);
}
if (this.status === REJECTED) {
  onrejected(this.value);
}
if (this.status === PENDING) {
  this.onfulfilledArr.push(onfulfilled);
  this.onrejectedArr.push(onrejected);
}

}
}

const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';

const test = new myPromise((resolve, reject) => {
setTimeout(() => {

resolve(100);

}, 2000)
});
test.then((data) => {
console.log(data);
},(data) => {});
能夠這樣理解

new myPromise 有異步代碼

setTimeout(() => {
    resolve(100);
}, 2000)

js是單線程的,這個時候會先把這個任務添加到定時觸發器線程中去(計時完畢後添加到事件隊列中,等待js引擎空閒後執行),先去執行下面的同步代碼
test.then((data) => {

    console.log(data);

},(data) => {});
完成輸出及狀態改變。

可是Promise的一大特色就是能夠鏈式調用,即test.then(success, fail).then(success, fail)...這就須要then返回一個新的Promise對象,而咱們的程序如今明顯的是不支持的。那麼繼續改一下。

2.3 實現鏈式調用

再觀察一下鏈式調用,若是成功和失敗的函數中有返回值,這個值要做爲參數傳給下個then函數的成功或失敗回調。因此咱們要在返回的new Promise中調用相應的函數。

class myPromise {
constructor (executor) {

this.status = PENDING;
this.value = '';
this.onfulfilledArr = [];
this.onrejectedArr = [];
this.resolve = this.resolve.bind(this);
this.reject = this.reject.bind(this);
this.then = this.then.bind(this);
executor(this.resolve, this.reject);

}

resolve (value) {

if (this.status === PENDING) {
  this.value = value;
  this.onfulfilledArr.forEach(item => {
    item(this.value);
  })
  this.status = FULFILLED; 
}

}

reject (value) {

if (this.status === PENDING) {
  this.value = value;
  this.onrejectedArr.forEach(item => {
    item(this.value);
  })
  this.status = REJECTED;
}

}

then (onfulfilled, onrejected) {

if (this.status === FULFILLED) {
  const res = onfulfilled(this.value);
  return new Promise(function(resolve, reject) {
    resolve(res);
  })
}
if (this.status === REJECTED) {
  const res = onrejected(this.value);
  return new Promise(function(resolve, reject) {
    reject(res);
  })
}
if (this.status === PENDING) {
  const self = this;
  return new Promise(function(resolve, reject) {
    self.onfulfilledArr.push(() => {
      const res = onfulfilled(self.value)
      resolve(res);
    });
    self.onrejectedArr.push(() => {
      const res = onrejected(self.value)
      reject(res);
    });
  })
}

}
}

const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';

const test = new myPromise((resolve, reject) => {
setTimeout(() => {

resolve(100);

}, 2000)
});
test.then((data) => {
console.log(data);
return data + 5;
},(data) => {})
.then((data) => {
console.log(data)
},(data) => {});

再運行一下,輸出100,105。好了,一個簡單的Promise就實現好了。

相關文章
相關標籤/搜索