本文引用文檔地址javascript
你們都知道 Promise 解決了回調地獄的問題,可是到底什麼是回調地獄? 他到底哪兒have some wrong ? 是由於嵌套回調仍是,仍是回調執行順序問題,仍是不夠美觀?html
來腦補一下,實現一個說話的需求,說完才能說下一句,那麼很容易的寫出下面這樣的一段代碼java
function say (word, callback) {
console.log(word)
setTimeout(() => {
callback&&callback()
},1000)
}
複製代碼
ok! fine!! 那麼如今要說話了node
say("first", function () {
say("second", function () {
say("third", function () {
console.log("end");
});
});
});
// first second third end
複製代碼
在這個例子中的嵌套的問題僅僅是縮進的問題,而縮進除了會讓代碼變寬可能會形成讀代碼的一點不方便以外,並無什麼其餘的問題。若是僅僅是這樣,爲何不叫「縮進地獄」或「嵌套地獄」?git
把回調地獄徹底理解成縮進的問題是常見的對回調地獄的誤解。要回到「回調地獄」這個詞語上面來,它的重點就在於「回調」,而「回調」在JS中應用最多的場景固然就是異步編程了。程序員
因此,「回調地獄」所說的嵌套實際上是指異步的嵌套。它帶來了兩個問題:可讀性的問題和信任問題es6
很容易找到一個這樣執行順序問題的問題github
for (var i = 0; i < 5; i++) {
setTimeout(function() {
console.log(new Date, i);
}, 1000);
}
console.log(new Date, i);
複製代碼
很好,結果不那麼重要,可是你總得想那麼一會吧。想代碼的執行順序問題,那麼你必須熟知異步代碼的執行機制,還要能很快的分析出想要的結果,ajax
so 你能作到麼。npm
好的,讓咱們接着腦補一下,你是華夏某銀行的少主,你的一個程序員寫下了一段代碼,調用了三方的ajax 扣款請求,可是因爲三方庫機制問題,調用了屢次。
那麼,你能很好的信任回調麼。還會不會有其餘的信任問題發生呢。
so
...
實際問題在於控制權轉交給了第三方,不可控的執行致使了咱們最頭疼的信任問題,實際在開發中,咱們須要檢查的狀態更多,依賴更多。信任問題帶來的成本,就大大致使了可讀性下降,須要更多的代碼來check。
雖然這些 fail 出現的機率並不高,可能在你編碼的時候都沒有關注到,可是實際上卻須要不少臃腫的代碼去強壯他,這就不夠友好了。
實際上萬能的開發者們也想了不少方法來解決信任問題
// 增長失敗回調
function sendAjax(onSuccess,onFail) {
if(success) {
onSuccess&&onSuccess()
}else{
onFail&&onFail()
}
}
複製代碼
// node error first
fs.stat(file, (err, stat) => {
if (err) {
doSomeThing()
} else {
doOtherThing()
}
})
複製代碼
實際上這並無解決全部的問題,諸如回調屢次執行,複雜場景下的回調問題。
小總結 異步回調帶來的問題主要集中在 可讀性差/信任問題
首先咱們來看一下 PromiseA+ 規範的第一句話
An open standard for sound, interoperable JavaScript promises—by implementers, for implementers.
一個開放的標準,用於實施者對實施者的聲音,可互操做的JavaScript保證。
A promise represents the eventual result of an asynchronous operation. The primary way of interacting with a promise is through its then method, which registers callbacks to receive either a promise’s eventual value or the reason why the promise cannot be fulfilled.
一個promise表示異步操做的最終結果。 與諾言互動的主要方式是經過then方法,該方法註冊回調以接收諾言的最終值或諾言沒法實現的緣由。
This specification details the behavior of the then method, providing an interoperable base which all Promises/A+ conformant promise implementations can be depended on to provide. As such, the specification should be considered very stable. Although the Promises/A+ organization may occasionally revise this specification with minor backward-compatible changes to address newly-discovered corner cases, we will integrate large or backward-incompatible changes only after careful consideration, discussion, and testing.
該規範詳細說明了then方法的行爲,提供了一個可互操做的基礎,全部Promises / A +符合諾言的實現均可以依靠該基礎來提供。 所以,該規範應被視爲很是穩定。 儘管Promises / A +組織有時會經過向後兼容的微小更改來修訂此規範,以解決新發現的極端狀況,但只有通過仔細考慮,討論和測試以後,咱們纔會集成大型或向後不兼容的更改。
1.0
Terminology 概念術語promise
is an object or function with a then method whose behavior conforms to this specification.thenable
is an object or function that defines a then method.value
is any legal JavaScript value (including undefined, a thenable, or a promise).exception
is a value that is thrown using the throw statement.reason
is a value that indicates why a promise was rejected.2.0
Requirements 要求 (實現)對於建立一個可互操做的javascript保證的標準來講,要解決的問題無非就是狀態管理,註冊回調事件,和可靠的承諾解決體系
**PromiseA+**規範實際上就從這三點出發,定義了三種標準。
那麼咱們來開始寫咱們的代碼,基於es6 class
class Promise {
constructor(executor) {
// PromiseA+...
}
}
module.exports = Promise;
複製代碼
2.1
Promise StatesA promise must be in one of three states: pending, fulfilled, or rejected.
一個Promise
必須處於pending``fulfilled``rejected
三種狀態之間
2.1.1
When pending, a promise:
2.1.1.1
may transition to either the fulfilled or rejected state.2.1.2
When fulfilled, a promise:
2.1.2.1
must not transition to any other state.2.1.2.2
must have a value, which must not change.2.1.3
When rejected, a promise:
2.1.3.1
must not transition to any other state.2.1.3.2
must have a reason, which must not change.Here, 「must not change」 means immutable identity (i.e. ===), but does not imply deep immutability.
這裏實際上定義了Promise
的狀態轉換關係,定義了
pending
狀態,能夠轉換爲其餘兩個狀態so!!! do it
class Promise {
constructor(executor) {
// 定義初始化狀態常量
this.PENDING = 'pending';//初始態
this.FULFILLED = 'fulfilled';//初始態
this.REJECTED = 'rejected';//初始態
// PromiseA+ 2.1.1.1
// 初始化狀態
this.status = this.PENDING;
// PromiseA+ 2.1
// 定義緩存經過then註冊的成功失敗回調的數組,支持 then 方法註冊多個回調
this.onResolveCallbacks = [];
this.onRejectCallbacks = [];
// 緩存this,避免this指向問題致使bug
const self = this;
// 定義成功失敗方法,做爲Promise 傳入的函數體的參數
// 實現PromiseA+狀態轉換 定義成功失敗參數
function reject(v) {
// Here, 「must not change」 means immutable identity (i.e. ===), but does not imply deep immutability.
const reason = v;
//PromiseA+ 2.1.3
if (self.status === self.PENDING) {
self.status = self.REJECTED;
self.value = reason;
self.onRejectCallbacks.forEach(item => item(self.value));
}
}
function resolve(value) {
//PromiseA+ 2.1.2
if (self.status === self.PENDING) {
self.status = self.FULFILLED;
self.value = value;
self.onResolveCallbacks.forEach(item => item(self.value));
}
}
// 開始執行函數體,捕獲錯誤。執行報錯則直接拒絕Promise
try {
executor(resolve, reject);
} catch (error) {
reject(error);
}
}
}
module.exports = Promise;
複製代碼
new Promise
過程已經實現了
2.2
The then MethodA promise must provide a then method to access its current or eventual value or reason.
A promise’s then method accepts two arguments:
promise.then(onFulfilled, onRejected)
複製代碼
那麼在類上定義方法
class Promise {
constructor(executor) {
...
}
// PromiseA+ 2.2 // PromiseA+ 2.2.6
then(onFulfilled, onRejected) {
}
}
module.exports = Promise;
複製代碼
2.2.1
Both onFulfilled and onRejected are optional arguments:
2.2.1.1
If onFulfilled is not a function, it must be ignored.2.2.1.2
If onRejected is not a function, it must be ignored.2.2.2
If onFulfilled is a function:
2.2.2.1
it must be called after promise is fulfilled, with promise’s value as its first argument.2.2.2.2
it must not be called before promise is fulfilled.2.2.2.3
it must not be called more than once.2.2.3
If onRejected is a function,
2.2.3.1
it must be called after promise is rejected, with promise’s reason as its first argument.2.2.3.2
it must not be called before promise is rejected.2.2.3.3
it must not be called more than once.
- 成功/失敗回調必須是一個
funciton
,不是函數將被忽略成功/失敗回調
必須在成功/失敗
以後被調用,第一個參數必須是value/reason
成功/失敗回調
不能調用屢次
2.2.4
onFulfilled or onRejected must not be called until the execution context stack contains only platform code. [3.1].
成功/失敗回調
不能在Promise 平臺代碼執行完以前被調用,意義在於防止調用時還有回調沒有被註冊進來
2.2.5
onFulfilled and onRejected must be called as functions (i.e. with no this value). [3.2]
成功/失敗回調
必須做爲函數被調用,若是拿到的參數不是函數,忽略它,生成默認的同步執行函數,以相同的值執行後續回調
2.2.6
then may be called multiple times on the same promise.
2.2.6.1
If/when promise is fulfilled, all respective onFulfilled callbacks must execute in the order of their originating calls to then.2.2.6.2
If/when promise is rejected, all respective onRejected callbacks must execute in the order of their originating calls to then.
then
方法可能被同一個promise
調用屢次then
方法註冊的成功/失敗回調
必須被以註冊的順序執行
2.2.7
then must return a promise [3.3].
2.2.7.1
If either onFulfilled or onRejected returns a value x, run the Promise Resolution Procedure [[Resolve]](promise2, x).2.2.7.2
If either onFulfilled or onRejected throws an exception e, promise2 must be rejected with e as the reason.2.2.7.3
If onFulfilled is not a function and promise1 is fulfilled, promise2 must be fulfilled with the same value as promise1.2.2.7.4
If onRejected is not a function and promise1 is rejected, promise2 must be rejected with the same reason as promise1.promise2 = promise1.then(onFulfilled, onRejected);
複製代碼
then
方法必須返回一個Promise instance
promise1
成功或則失敗回調正確執行拿到一個返回值value x
運行Promise Resolution Procedure
解析程序[Resolve]](promise2, x)
promise1
拒絕throws an exception e
,就以相同的緣由拒絕promise2
promise1
的成功/失敗回調
不是一個function
並且promise1
成功/失敗時,promise2
必須以相同的value
/e
被成功或者拒絕
so!!! do it!!!
PromiseA+ 2.2.4
onFulfilled or onRejected must not be called until the execution context stack contains only platform code.
爲了防止在平臺代碼執行完畢,徹底註冊回調以前調用回調,採用宏任務setTimeout0
實現 ::: details 展開查看constructor
constructor(executor) {
this.PENDING = 'pending';//初始態
this.FULFILLED = 'fulfilled';//初始態
this.REJECTED = 'rejected';//初始態
//PromiseA+ 2.1.1.1
this.status = this.PENDING;
// PromiseA+ 2.1
this.onResolveCallbacks = [];
this.onRejectCallbacks = [];
const self = this;
function reject(v) {
const reason = v;
// PromiseA+ 2.2.4
setTimeout(() => {
//PromiseA+ 2.1.3
if (self.status === self.PENDING) {
self.status = self.REJECTED;
self.value = reason;
// console.dir(self);
// console.log('------self--------------------------------');
self.onRejectCallbacks.forEach(item => item(self.value));
}
});
}
function resolve(value) {
// PromiseA+ 2.2.4
setTimeout(() => {
//PromiseA+ 2.1.2
if (self.status === self.PENDING) {
self.status = self.FULFILLED;
self.value = value;
self.onResolveCallbacks.forEach(item => item(self.value));
}
});
}
try {
executor(resolve, reject);
} catch (error) {
reject(error);
}
}
複製代碼
:::
class Promise {
constructor(executor) {
...
}
resolvePromise(promise2, x, resolve, reject) {
}
// PromiseA+ 2.2 // PromiseA+ 2.2.6
then(onFulfilled, onRejected) {
//緩存this
const self = this;
//PromiseA+ 2.2.1 / PromiseA+ 2.2.5 / PromiseA+ 2.2.7.3 / PromiseA+ 2.2.7.4
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : x => x;
onRejected = typeof onRejected === 'function' ? onRejected : e => { throw e; };
let promise2;
function fulfillCallback(resolve, reject) {
// PromiseA+ 2.2.4
setTimeout(() => {
try {
const x = onFulfilled(self.value);
//PromiseA+ 2.2.7.1
self.resolvePromise(promise2, x, resolve, reject);
} catch (error) {
//PromiseA+ 2.2.7.2
reject(error);
}
});
}
function rejectCallback(resolve, reject) {
// PromiseA+ 2.2.4
setTimeout(() => {
try {
const e = onRejected(self.value);
//PromiseA+ 2.2.7.1
self.resolvePromise(promise2, e, resolve, reject);
} catch (error) {
//PromiseA+ 2.2.7.2
reject(error);
}
});
}
// PromiseA+ 2.2.2
if (self.status === self.FULFILLED) {
//PromiseA+ 2.2.7
return promise2 = new Promise((resolve, reject) => {
fulfillCallback(resolve, reject);
});
}
// PromiseA+ 2.2.3
if (self.status === self.REJECTED) {
//PromiseA+ 2.2.7
return promise2 = new Promise((resolve, reject) => {
rejectCallback(resolve, reject);
});
}
if (self.status === self.PENDING) {
//PromiseA+ 2.2.7
return promise2 = new Promise((resolve, reject) => {
self.onResolveCallbacks.push(() => {
fulfillCallback(resolve, reject);
});
self.onRejectCallbacks.push(() => {
rejectCallback(resolve, reject);
});
});
}
}
}
module.exports = Promise;
複製代碼
so 2.2.7.1 是個什麼鬼!!鬼!!鬼啊!!!!
接着看下去吧
2.3
The Promise Resolution Procedure::: danger 交互性 javascript 保證 If either onFulfilled or onRejected returns a value x, run the Promise Resolution Procedure [[Resolve]](promise2, x).
promise1
成功或則失敗回調正確執行拿到一個返回值value x
運行Promise Resolution Procedure
解析程序[Resolve]](promise2, x)
:::
The promise resolution procedure is an abstract operation taking as input a promise and a value, which we denote as [[Resolve]](promise, x). If x is a thenable, it attempts to make promise adopt the state of x, under the assumption that x behaves at least somewhat like a promise. Otherwise, it fulfills promise with the value x.
This treatment of thenables allows promise implementations to interoperate, as long as they expose a Promises/A+-compliant then method. It also allows Promises/A+ implementations to 「assimilate」 nonconformant implementations with reasonable then methods.
promise resolution procedure
promise 解決程序其實是一種承諾實現的抽象,將promise
和值x
做爲輸入,表示爲[[Resolve]](promise,x)
若是x是可能的,則在x的行爲至少相似於承諾的假設下,嘗試使承諾採用x的狀態。 不然,它將以值x履行承諾。這種對可實現對象的處理使答應實現能夠互操做,只要它們公開了符合Promises / A +的then方法便可。 它還容許Promises / A +實現使用合理的then方法「整合」不合格的實現。
想要運行 promise resolution procedure
, 須要遵循瞎下面的規範。
2.3.1
If promise and x refer to the same object, reject promise with a TypeError as the reason.若是
promise
和x
指向同一個對象,以一個TypeError
做爲緣由拒絕promise
2.3.2
If x is a promise, adopt its state [3.4]:
2.3.2.1
If x is pending, promise must remain pending until x is fulfilled or rejected.2.3.2.2
If/when x is fulfilled, fulfill promise with the same value.2.3.2.3
If/when x is rejected, reject promise with the same reason.若是
x
是一個promise
(是本身的實例instanceof
) 採用下面的狀態
- 若是
x
在pending
狀態,promise2
必須保持pending
狀態直到x
被fulfilled/rejected
- 若是
x
被fulfilled/rejected
,promise2
必須保持x
相同的value/reason
被fulfilled/rejected
2.3.3
Otherwise, if x is an object or function,
2.3.3.1
Let then be x.then. [3.5]
2.3.3.2
If retrieving the property x.then results in a thrown exception e, reject promise with e as the reason.
2.3.3.3
If then is a function, call it with x as this, first argument resolvePromise, and second argument rejectPromise, where:
2.3.3.3.1
If/when resolvePromise is called with a value y, run [[Resolve]](promise, y).
2.3.3.3.2
If/when rejectPromise is called with a reason r, reject promise with r.
2.3.3.3.3
If both resolvePromise and rejectPromise are called, or multiple calls to the same argument are made, the first call takes precedence, and any further calls are ignored.
2.3.3.3.4
If calling then throws an exception e,
2.3.3.3.4.1
If resolvePromise or rejectPromise have been called, ignore it.2.3.3.3.4.2
Otherwise, reject promise with e as the reason.2.3.3.4
If then is not a function, fulfill promise with x.
2.3.3.4
If x is not an object or function, fulfill promise with x.
當
x
是一個object
orfunction
此時多是一個同步的處理函數,也多是一個thenable
對象
- 訪問
x
的then
屬性 實際上該操做可能會報錯,通常來講訪問一個對象的屬性不會報錯,可是若是該屬性是一個getter
的時候,在執行getter
的時候可能會拋異常e
。此時應該以e
來拒絕peomise2
- 當
then
是一個function
,經過then.call(x)
調用它,同時給x
註冊成功處理函數和失敗處理函數,
- 當成功回調被執行並傳入
y
的時候,運行[[Resolve]](promise, y)
繼續解析。- 當失敗回調被執行並傳入
e
的時候,把e
做爲reason
拒絕promise2
- 若是成功失敗回調被屢次調用,那麼第一次的調用將優先調用,其餘的調用將被忽略,這裏須要添加
called
標誌是否被調用,在每次調用成功失敗時校驗,並調用時立馬修改標誌位狀態- 若是x不是
function
對象那麼以x
實現promise
- 若是x不是
thenable
對象那麼以x
實現promise
解析程序實際上保證了promise
的可靠性,對thenable
對象狀態的判斷,循環解析,直到x
做爲一個普通的不能在被解析的非thenable
才實現調用,對錯誤的處理也貫徹整個流程,並且保證了調用的惟一性。 這實現了那句可互操做的JavaScript保證。
那麼,讓咱們來一步一步的實現它吧
resolvePromise(promise2, x, resolve, reject) {
const self = this;
// PromiseA+ 2.3.1
if (promise2 === x) { return reject(new TypeError('循環引用')); }
// PromiseA+ 2.3.2
if (x instanceof Promise) {
if (x.status === self.PENDING) {
// PromiseA+ 2.3.2.1
x.then(function(y) { self.resolvePromise(promise2, y, resolve, reject); }, reject);
} else {
// PromiseA+ 2.3.2.2 /PromiseA+ 2.3.2.3
x.then(resolve, reject);
}
// PromiseA+ 2.3.3
} else if (x && ((typeof x === 'object') || (typeof x === 'function'))) {
// PromiseA+ 2.3.3.3.3 / PromiseA+ 2.3.3.3.4.1
let called = false;
try {
// PromiseA+ 2.3.3.1
const then = x.then;
// PromiseA+ 2.3.3.3
if (typeof then === 'function') {
try {
then.call(
x,
function(y) {
if (called) return;
called = true;
// PromiseA+ 2.3.3.3.1
self.resolvePromise(promise2, y, resolve, reject);
},
function(e) {
if (called) return;
called = true;
// PromiseA+ 2.3.3.3.2
reject(e);
}
);
} catch (e) {
if (called) return;
called = true;
// PromiseA+ 2.3.3.3.2
reject(e);
}
} else {
// PromiseA+ 2.3.3.4
resolve(x);
}
} catch (error) {
if (called) return;
called = true;
// PromiseA+ 2.3.3.2 / PromiseA+ 2.3.3.4.2
reject(error);
}
} else {
// PromiseA+ 2.3.4
resolve(x);
}
}
複製代碼
到此PromiseA+
規範已經徹底實現,原則上全部的Promise
庫都應該遵循此規範,現代瀏覽器支持的promise
都支持一些Promise.all
,Promise.race
,Promise.resolve
,Promise.reject
,接下來讓咱們來實現它
//all 實現
Promise.all = function(promises) {
//promises是一個promise的數組
return new Promise(function (resolve, reject) {
const arr = []; //arr是最終返回值的結果
let i = 0; // 表示成功了多少次
function processData(index, y) {
arr[index] = y;
if (++i === promises.length) {
resolve(arr);
}
}
for (let i = 0; i < promises.length; i++) {
promises[i].then(function (y) {
processData(i, y);
}, reject);
}
});
};
// race 實現
Promise.race = function (promises) {
return new Promise(function (resolve, reject) {
for (let i = 0; i < promises.length; i++) {
promises[i].then(resolve, reject);
}
});
};
// Promise.resolve 實現
Promise.resolve = function (value) {
return new Promise(function(resolve, reject) {
resolve(value);
});
};
// Promise.reject 實現
Promise.reject = function (reason) {
return new Promise(function(resolve, reject) {
reject(reason);
});
};
複製代碼
npm install promises-aplus-tests -g
複製代碼
測試套件其實是一個cli命令行工具,只須要在Promise 上暴露出一個fucntion
接口deferred
,函數返回一個對象,對象包含一個Promise 實例,和實例的resolve,reject 參數
Adapters In order to test your promise library, you must expose a very minimal adapter interface. These are written as Node.js modules with a few well-known exports:
Promise.deferred = function () {
const defer = {};
defer.promise = new Promise(function (resolve, reject) {
defer.resolve = resolve;
defer.reject = reject;
});
return defer;
};
複製代碼
進入到Promise.js
所在目錄,運行
promises-aplus-tests ./Promise.js
複製代碼
or in package.json
"scripts": {
"testa:promise": "promises-aplus-tests ./src/promise/Promise.js"
},
複製代碼
那麼順利的話! 你講看到這個