對於Promise
,相信大多數人都已經瞭解而且能夠熟練的使用它的各類方法,可是追究它的底層原理,可能並不清楚,這篇文章是本身在通過一段時間的學習後寫出來的,裏面包含了本身對Promise
的理解,而且按照步驟一步一步的手寫了Promise
以及then
方法,但願這篇文章能夠幫助到大家。 編程
Promise
是異步編程的一種解決方案,ES6將其寫進了語言標準。所謂Promise
就是一個容器,裏面保存着將來纔會結束的事件(一般是一個異步操做)的結果。promise
Promise
對象有如下兩個特色:瀏覽器
Promise
對象表明一個異步操做,有三種狀態:pending
(進行中)、fulfilled/resolved
(已成功)、rejected
(已失敗)。只有異步操做的結果,能夠決定當前是哪種狀態,任何其餘操做都沒法改變這個狀態。Promise
對象的狀態改變,只有兩種可能:從pending
變爲fulfilled
和從pending
變爲rejected
。只要這兩種狀況發生,狀態就凝固了,不會再變了,會一直保持這個結果,這時就稱爲 resolved
(已定型)。若是改變已經發生了,你再對Promise
對象添加回調函數,也會當即獲得這個結果。Promise
的優勢:bash
能夠將異步操做以同步操做的流程表達出來,避免了層層嵌套的回調函數。異步
ES6 規定,Promise
對象是一個構造函數,用來生成Promise
實例。async
const promise = new Promise((resolve,reject)=>{
//此處執行一些異步操做(調用後臺API,定時器等)
if(/*異步操做成功*/){
resolve(value);
}else{
reject(error)
}
})
//其中兩個函數的參數值分別爲成功和失敗後想要傳遞的結果
複製代碼
Promise
構造函數接受一個函數做爲參數,該函數的兩個參數分別是resolve
和reject
。它們是兩個函數,由 JavaScript
引擎提供,不用本身部署。異步編程
resolve
函數的做用是,將Promise
對象的狀態從「未完成」變爲「成功」(即從 pending
變爲 resolved
),在異步操做成功時調用,並將異步操做的結果,做爲參數傳遞出去;reject
函數的做用是,將Promise
對象的狀態從「未完成」變爲「失敗」(即從 pending
變爲 rejected
),在異步操做失敗時調用,並將異步操做報出的錯誤,做爲參數傳遞出去。函數
then
方法能夠接受兩個回調函數做爲參數。第一個回調函數是Promise
對象的狀態變爲resolved
時調用,第二個回調函數是Promise
對象的狀態變爲rejected
時調用。其中,第二個函數是可選的,不必定要提供。這兩個函數都接受Promise
對象傳出的值做爲參數。post
promise.then(res=>{
//對於成功回調接受的數據作處理
},err=>{
//對於失敗的回調數據作處理
})
複製代碼
注:Promise
新建後就會當即執行。學習
Promise.prototype.then()
Promise
實例具備then
方法,也就是說,then
方法是定義在原型對象上Promise.prototype
上的,它的做用是爲 Promise
實例添加狀態改變時的回調函數。前面說過,then
方法的第一個參數是resolved
狀態的回調函數,第二個參數(可選)是rejected
狀態的回調函數。
then
方法返回的是一個新的Promise
實例(注意,不是原來那個Promise
實例)。所以能夠採用鏈式寫法,即then
方法後面再調用另外一個then
方法。第一個回調函數完成之後,會將返回結果做爲參數,春如第二個回調函數。
採用鏈式的 then
,能夠指定一組按照次序調用的回調函數。(ES7中的async/await
)也能夠實現鏈式調用,除此以外,Promise
的all
方法能夠實現並行執行。
瞭解了基礎的 Promise
和 then
以後,咱們即可以本身建立一個 Promise
。
首先寫 Promise
構造函數,由以上Promise
的使用可知,其參數爲一個函數,又被稱爲執行器函數(executor),而且執行器函數會被當即調用,執行器函數也會接收兩個參數,且這兩個參數均爲函數。
function Promise(executor) {
executor(resolve, reject);
}
複製代碼
Promise
最重要的方法就是then
方法,所以爲了可以讓實例調用這個方法,咱們必須將這個方法寫在其原型鏈上,而且它接受兩個參數,一個爲成功的回調,一個爲失敗得回調。
Promise.prototype.then=function(onResolved,onRejected){
}
複製代碼
以後繼續寫Promise
函數,由於new
出來的實例具備默認的狀態pending
,以後經過執行器executor
執行 resolve
和reject
兩個函數來修改狀態。
function Promise(executor) {
let self=this; //保留this。防止後面方法出現this只想不明的問題
self.status='pending'; //promise的默認狀態是pending
function resolve(){
self.status='resolved'; //成功函數將其狀態修改成resolved
}
function reject(){
self.status='rejected'; //失敗函數將其函數修改成rejected
}
executor(resolve, reject);
}
複製代碼
爲了保證 Promise
實例狀態一旦變動不能再次改變,須要進行判斷
function Promise(executor) {
let self = this; //保留this。防止後面方法出現this只想不明的問題
self.status = 'pending'; //promise的默認狀態是pending
self.success = undefined; //保存成功回調傳遞的值
self.error = undefined; //保存失敗回調傳遞的值
function resolve() {
if (self.status === 'pending') {
self.status = 'resolved'; //成功函數將其狀態修改成resolved
}
}
function reject() {
if (self.status === 'pending') {
self.status = 'rejected'; //失敗函數將其函數修改成rejected
}
}
executor(resolve, reject);
}
複製代碼
以後須要將調用以後的成功或失敗的結果保存起來
function Promise(executor) {
let self = this; //保留this。防止後面方法出現this只想不明的問題
self.status = 'pending'; //promise的默認狀態是pending
self.success = undefined; //保存成功回調傳遞的值
self.error = undefined; //保存失敗回調傳遞的值
function resolve(success) {
if (self.status === 'pending') {
self.status = 'resolved'; //成功函數將其狀態修改成resolved
self.success=success; //將成功的值保存起來
}
}
function reject(error) {
if (self.status === 'pending') {
self.status = 'rejected'; //失敗函數將其函數修改成rejected
self.error=error; //將失敗的值保存起來
}
}
executor(resolve, reject);
}
複製代碼
在這裏舉一個實際的例子(Express使用Promise保存的須要返回的值)
注意:該例子涉及到了異步函數處理,鏈式調用,放在此處只是爲了說明上面的概念。
當執行器調用 resolve
函數後,then
中的第一個參數函數(成功回調)會執行,並將保存的值傳遞給then
中的第一個函數做爲參數,同時當執行器調用 reject
函數後,then
中的第二個參數函數(失敗回調)會執行,並將保存的值傳遞給then
中的第二個函數做爲參數。
Promise.prototype.then = function (onResolved, onRejected) {
let self = this;
if (self.status === 'resolved'); {
onResolved(self.success); //將resolve函數保留的成功值傳遞做爲參數
}
if (self.status === 'rejected') {
onRejected(self.error); //將reject函數保留的失敗值傳遞做爲參數
}
}
複製代碼
對應於上面的例子,舉出其then
的使用
到此爲止Promise
的簡單結構已經基本完成,簡單測試
let promise = new Promise((resolve, reject) => {
console.log('start');
resolve('success data');
})
promise.then(res => {
console.log("res", res);
}, err => {
console.log("err", err);
})
複製代碼
測試結果
start
res success data
複製代碼
以上步驟只是實現了同步處理,接下來實現異步處理以及實現一個實例屢次調用then
方法(不是鏈式調用)
由於 js
是單線程的,簡單理解瀏覽器端的事件循環即爲先執行同步任務,後執行異步任務。同步任務是存放在調用棧中的,主線程會先執行同步任務,當調用棧中的同步任務全都執行完畢且主線程爲空時,主線程會去任務隊列中查找是否有已經註冊的異步任務的回調函數,有則執行,無則等待。任務隊列中的異步任務又分爲微任務和宏任務,這二者也有相應的執行順序。詳細介紹能夠等下篇文章
言歸正傳,實現異步處理及屢次調用
若是Promise
處理的爲一個異步函數,那麼當then
的時候,執行器函數中的參數會被放到異步任務隊列中,即爲此時Promise
的實例仍爲默認狀態pending
,沒有改變,那麼咱們此時並不知道要去執行then
中的成功回調函數仍是失敗回調函數,在不知道哪一個回調函數會被執行的狀況下,就須要把這兩個回調函數保存起來,等到時機成熟,肯定哪一個函數的時候,再拿出來調用。
function Promise(executor) {
let self = this; //保留this。防止後面方法出現this只想不明的問題
self.status = 'pending'; //promise的默認狀態是pending
self.success = undefined; //保存成功回調傳遞的值
self.error = undefined; //保存失敗回調傳遞的值
self.onSuccessCallbacks = []; //存放成功的回調
self.onErrorCallbacks = []; //存放失敗的回調
function resolve(success) {
if (self.status === 'pending') {
self.status = 'resolved'; //成功函數將其狀態修改成resolved
self.success = success; //將成功的值保存起來
self.onSuccessCallbacks.forEach(element => {
element();
});
}
}
function reject(error) {
if (self.status === 'pending') {
self.status = 'rejected'; //失敗函數將其函數修改成rejected
self.error = error; //將失敗的值保存起來
self.onErrorCallbacks.forEach(element => {
element();
})
}
}
executor(resolve, reject);
}
Promise.prototype.then = function (onResolved, onRejected) {
let self = this;
if (self.status === 'pending') {
self.onSuccessCallbacks.push(() => {
onResolved(self.success); //將resolve函數保留的成功值傳遞做爲參數
})
self.onErrorCallbacks.push(() => {
onRejected(self.error); //將reject函數保留的失敗值傳遞做爲參數
})
}
if (self.status === 'resolved') {
onResolved(self.success); //將resolve函數保留的成功值傳遞做爲參數
}
if (self.status === 'rejected') {
onRejected(self.error); //將reject函數保留的失敗值傳遞做爲參數
}
}
複製代碼
測試用例
let promise = new Promise((resolve, reject) => {
setTimeout(function () {
resolve('success data')
}, 2000)
})
promise.then(res => {
console.log("success:", res);
}, err => {
console.log("error:", err);
})
promise.then(res => {
console.log("success:", res);
}, err => {
console.log("error:", err);
})
複製代碼
測試結果爲2秒後出現結果
success: success data
success: success data
複製代碼
繼續進行嘗試,若是讓Promise
拋出一個錯誤如何處理
let promise = new Promise((resolve, reject) => {
throw new error("一個錯誤");
})
promise.then(res => {
console.log("success:", res);
}, err => {
console.log("error:", err);
})
複製代碼
結果:
解決該問題
try {
executor(resolve, reject);
} catch (err) {
reject(err);
}
複製代碼
再次嘗試,查看結果
executor
函數進行異常處理,若是出錯了就直接進入
reject
方法。
完成上面的一系列完善以後,最後咱們實現Promise
的鏈式調用。
Promise
實現鏈式調用就是經過then
方法返回一個新的Promise
。
若是返回的是一個Promise
函數,那麼會等待這個Promise
執行完成以後再返回給下一次的then
,Promise
若是成功,就會走下一次then
的成功,若是失敗就會走下一次then
的失敗。
注意:then
方法中返回的回調函數不能是本身自己,若是真的這樣寫,那麼函數執行到裏面時會等待promise
的結果,這樣一層層的狀態等待就會造成回調地獄。
接下來一步步分析(只須要改進then
函數便可)
then
函數中嵌套new Promise
以後主要爲resolvePromise
函數,對x
進行判斷,作出相應的操做:
到此基本功能已經完成,如下爲源碼以及測試例子及結果
源碼:
//Promise函數
function Promise(executor) {
let self = this; //保留this。防止後面方法出現this只想不明的問題
self.status = 'pending'; //promise的默認狀態是pending
self.success = undefined; //保存成功回調傳遞的值
self.error = undefined; //保存失敗回調傳遞的值
self.onSuccessCallbacks = []; //存放成功的回調
self.onErrorCallbacks = []; //存放失敗的回調
function resolve(success) {
if (self.status === 'pending') {
self.status = 'resolved'; //成功函數將其狀態修改成resolved
self.success = success; //將成功的值保存起來
self.onSuccessCallbacks.forEach(element => {
element();
});
}
}
function reject(error) {
if (self.status === 'pending') {
self.status = 'rejected'; //失敗函數將其函數修改成rejected
self.error = error; //將失敗的值保存起來
self.onErrorCallbacks.forEach(element => {
element();
})
}
}
try {
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
//then函數
Promise.prototype.then = function (onResolved, onRejected) {
let self = this;
let promiseAgain = new Promise((resolve, reject) => {
if (self.status === 'pending') {
self.onSuccessCallbacks.push(() => {
let x = onResolved(self.success); //將resolve函數保留的成功值傳遞做爲參數
resolvePromise(promiseAgain, x, resolve, reject);
})
self.onErrorCallbacks.push(() => {
let x = onRejected(self.error); //將reject函數保留的失敗值傳遞做爲參數
resolvePromise(promiseAgain, x, resolve, reject);
})
}
if (self.status === 'resolved') {
let x = onResolved(self.success); //將resolve函數保留的成功值傳遞做爲參數
resolvePromise(promiseAgain, x, resolve, reject);
}
if (self.status === 'rejected') {
let x = onRejected(self.error); //將reject函數保留的失敗值傳遞做爲參數
resolvePromise(promiseAgain, x, resolve, reject);
}
})
return promiseAgain;
}
//resolvePromise函數
function resolvePromise(promiseAgain, x, resolve, reject) {
if (promiseAgain === x) {
return reject(new TypeError("循環調用"));
}
if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
try {
let then = x.then;
if (typeof then === 'function') {
then.call(x, (y) => {
resolvePromise(promiseAgain, y, resolve, reject);
}, (e) => {
reject(e);
})
} else {
resolve(x);
}
} catch (error) {
reject(error);
}
} else {
resolve(x);
}
}
module.exports = Promise;
複製代碼
測試示例:
let Promise = require('./Promise');
let promise = new Promise((resolve, reject) => {
setTimeout(function () {
resolve('success data')
}, 2000)
})
promise.then(res => {
console.log("第一次調用", res);
return res;
}, err => {
console.log("error:", err);
})
.then(res => {
console.log("第二次調用",res);
return res
}, err => {
console.log("err", err);
})
.then(res => {
console.log("第三次調用",res);
}, err => {
console.log("err", err);
})
複製代碼
測試結果:
第一次調用 success data
第二次調用 success data
第三次調用 success data
複製代碼
使用過Promise
,咱們隨口而出即爲Promise
爲異步函數,其實Promise
在實例化(new
的過程)的時候是同步的,而then
中註冊的回調纔是異步執行的。
let Promise = require('./Promise');
let promise = new Promise((resolve, reject) => {
console.log("其次會被執行");
resolve("success data");
})
promise.then(res => {
console.log("第一次調用", res);
// return res;
}, err => {
console.log("error:", err);
})
console.log("首先會被執行");
複製代碼
執行結果:
其次會被執行
第一次調用 success data
首先會被執行
複製代碼
最終代碼:
//Promise函數
function Promise(executor) {
let self = this; //保留this。防止後面方法出現this只想不明的問題
self.status = 'pending'; //promise的默認狀態是pending
self.success = undefined; //保存成功回調傳遞的值
self.error = undefined; //保存失敗回調傳遞的值
self.onSuccessCallbacks = []; //存放成功的回調
self.onErrorCallbacks = []; //存放失敗的回調
function resolve(success) {
if (self.status === 'pending') {
self.status = 'resolved'; //成功函數將其狀態修改成resolved
self.success = success; //將成功的值保存起來
self.onSuccessCallbacks.forEach(element => {
element();
});
}
}
function reject(error) {
if (self.status === 'pending') {
self.status = 'rejected'; //失敗函數將其函數修改成rejected
self.error = error; //將失敗的值保存起來
self.onErrorCallbacks.forEach(element => {
element();
})
}
}
try {
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
//then函數
Promise.prototype.then = function (onResolved, onRejected) {
onResolved = typeof onResolved == 'function' ? onResolved : val => val;
onRejected = typeof onRejected == 'function' ? onRejected : err => {
throw err;
}
let self = this;
let promiseAgain = new Promise((resolve, reject) => {
if (self.status === 'pending') {
self.onSuccessCallbacks.push(() => {
try {
let x = onResolved(self.success); //將resolve函數保留的成功值傳遞做爲參數
resolvePromise(promiseAgain, x, resolve, reject);
} catch (e) {
reject(e)
}
})
self.onErrorCallbacks.push(() => {
try {
let x = onRejected(self.error); //將reject函數保留的失敗值傳遞做爲參數
resolvePromise(promiseAgain, x, resolve, reject);
} catch (e) {
reject(e)
}
})
}
if (self.status === 'resolved') {
try {
let x = onResolved(self.success); //將resolve函數保留的成功值傳遞做爲參數
resolvePromise(promiseAgain, x, resolve, reject);
} catch (e) {
reject(e)
}
}
if (self.status === 'rejected') {
try {
let x = onRejected(self.error); //將reject函數保留的失敗值傳遞做爲參數
resolvePromise(promiseAgain, x, resolve, reject);
} catch (e) {
reject(e)
}
}
})
return promiseAgain;
}
//resolvePromise函數
function resolvePromise(promiseAgain, x, resolve, reject) {
if (promiseAgain === x) {
return reject(new TypeError("循環調用"));
}
if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
try {
let then = x.then;
if (typeof then === 'function') {
then.call(x, (y) => {
resolvePromise(promiseAgain, y, resolve, reject);
}, (e) => {
reject(e);
})
} else {
resolve(x);
}
} catch (error) {
reject(error);
}
} else {
resolve(x);
}
}
module.exports = Promise;
複製代碼
測試實例:
let Promise = require('./Promise');
let promise = new Promise((resolve, reject) => {
setTimeout(function () {
resolve('success data')
}, 0)
})
promise.then(res => {
console.log("第一次調用", res);
return res;
}, err => {
console.log("error:", err);
})
.then(res => {
console.log("第二次調用",res);
return res
}, err => {
console.log("err", err);
})
.then(res => {
console.log("第三次調用",res);
}, err => {
console.log("err", err);
})
console.log("首先會被執行");
複製代碼
測試結果:
首先會被執行
第一次調用 success data
第二次調用 success data
第三次調用 success data
複製代碼
此篇文章只是本身在瞭解Promise
源碼以後,結合本身的理解寫下的,但願這篇文章能夠有所幫助。
在最後推薦一下各位讀者在瞭解Promise
的時候,能夠了解一下瀏覽器的事件循環,這對於一段混雜着setTimeOut
,Promise
的輸出順序有好大的幫助,這裏推薦一篇文章瀏覽器端事件循環,以後逼着可能也會寫一下瀏覽器的事件循環文章。