在使用[`Promise.resolve(value)`](http://liubin.github.io/promises-book/#Promise.resolve)?等方法的時候,如果promise對象立刻就能進入resolve狀態的話,那么你是不是覺得?`.then`?里面指定的方法就是同步調用的呢?
實際上,?`.then`?中指定的方法調用是異步進行的。
~~~
var promise = new Promise(function (resolve){
console.log("inner promise"); // 1
resolve(42);
});
promise.then(function(value){
console.log(value); // 3
});
console.log("outer promise"); // 2
~~~
執行上面的代碼會輸出下面的log,從這些log我們清楚地知道了上面代碼的執行順序。
~~~
inner promise // 1
outer promise // 2
42 // 3
~~~
由于JavaScript代碼會按照文件的從上到下的順序執行,所以最開始?`<1>`?會執行,然后是?`resolve(42);`?被執行。這時候?`promise`?對象的已經變為確定狀態,FulFilled被設置為了?`42`?。
下面的代碼?`promise.then`?注冊了?`<3>`?這個回調函數,這是本專欄的焦點問題。
由于?`promise.then`?執行的時候promise對象已經是確定狀態,從程序上說對回調函數進行同步調用也是行得通的。
但是即使在調用?`promise.then`?注冊回調函數的時候promise對象已經是確定的狀態,Promise也會以異步的方式調用該回調函數,這是在Promise設計上的規定方針。
因此?`<2>`?會最先被調用,最后才會調用回調函數?`<3>`?。
為什么要對明明可以以同步方式進行調用的函數,非要使用異步的調用方式呢?
## 2.3.1\. 同步調用和異步調用同時存在導致的混亂
其實在Promise之外也存在這個問題,這里我們以一般的使用情況來考慮此問題。
這個問題的本質是接收回調函數的函數,會根據具體的執行情況,可以選擇是以同步還是異步的方式對回調函數進行調用。
下面我們以?`onReady(fn)`?為例進行說明,這個函數會接收一個回調函數進行處理。
mixed-onready.js
~~~
function onReady(fn) {
var readyState = document.readyState;
if (readyState === 'interactive' || readyState === 'complete') {
fn();
} else {
window.addEventListener('DOMContentLoaded', fn);
}
}
onReady(function () {
console.log('DOM fully loaded and parsed');
});
console.log('==Starting==');
~~~
[mixed-onready.js](http://liubin.github.io/promises-book/#mixed-onready.js)會根據執行時DOM是否已經裝載完畢來決定是對回調函數進行同步調用還是異步調用。
如果在調用onReady之前DOM已經載入的話
對回調函數進行同步調用
如果在調用onReady之前DOM還沒有載入的話
通過注冊?`DOMContentLoaded`?事件監聽器來對回調函數進行異步調用
因此,如果這段代碼在源文件中出現的位置不同,在控制臺上打印的log消息順序也會不同。
為了解決這個問題,我們可以選擇統一使用異步調用的方式。
async-onready.js
~~~
function onReady(fn) {
var readyState = document.readyState;
if (readyState === 'interactive' || readyState === 'complete') {
setTimeout(fn, 0);
} else {
window.addEventListener('DOMContentLoaded', fn);
}
}
onReady(function () {
console.log('DOM fully loaded and parsed');
});
console.log('==Starting==');
~~~
關于這個問題,在?[Effective JavaScript](http://effectivejs.com/)?的?**第67項 不要對異步回調函數進行同步調用**?中也有詳細介紹。
> * 絕對不能對異步回調函數(即使在數據已經就緒)進行同步調用。
> * 如果對異步回調函數進行同步調用的話,處理順序可能會與預期不符,可能帶來意料之外的后果。
> * 對異步回調函數進行同步調用,還可能導致棧溢出或異常處理錯亂等問題。
> * 如果想在將來某時刻調用異步回調函數的話,可以使用?`setTimeout`?等異步API。
>
>
> Effective JavaScript
> — David Herman
前面我們看到的?`promise.then`?也屬于此類,為了避免上述中同時使用同步、異步調用可能引起的混亂問題,Promise在規范上規定**Promise只能使用異步調用方式**?。
最后,如果將上面的?`onReady`?函數用Promise重寫的話,代碼如下面所示。
onready-as-promise.js
~~~
function onReadyPromise() {
return new Promise(function (resolve, reject) {
var readyState = document.readyState;
if (readyState === 'interactive' || readyState === 'complete') {
resolve();
} else {
window.addEventListener('DOMContentLoaded', resolve);
}
});
}
onReadyPromise().then(function () {
console.log('DOM fully loaded and parsed');
});
console.log('==Starting==');
~~~
由于Promise保證了每次調用都是以異步方式進行的,所以我們在實際編碼中不需要調用?`setTimeout`?來自己實現異步調用。
- 前言
- 第一章 - 什么是Promise
- 1.1. 什么是Promise
- 1.2. Promise簡介
- 1.3. 編寫Promise代碼
- 第二章 - 實戰Promise
- 2.1. Promise.resolve
- 2.2. Promise.reject
- 2.3. 專欄: Promise只能進行異步操作?
- 2.4. Promise#then
- 2.5. Promise#catch
- 2.6. 專欄: 每次調用then都會返回一個新創建的promise對象
- 2.7. Promise和數組
- 2.8. Promise.all
- 2.9. Promise.race
- 2.10. then or catch?
- 第三章 - Promise測試
- 3.1. 基本測試
- 3.2. Mocha對Promise的支持
- 3.3. 編寫可控測試(controllable tests)
- 第四章 - Advanced
- 4.1. Promise的實現類庫(Library)
- 4.2. Promise.resolve和Thenable
- 4.3. 使用reject而不是throw
- 4.4. Deferred和Promise
- 4.5. 使用Promise.race和delay取消XHR請求
- 4.6. 什么是 Promise.prototype.done ?
- 4.7. Promise和方法鏈(method chain)
- 4.8. 使用Promise進行順序(sequence)處理
- 第五章 - Promises API Reference
- 5.1. Promise#then
- 5.2. Promise#catch
- 5.3. Promise.resolve
- 5.4. Promise.reject
- 5.5. Promise.all
- 5.6. Promise.race
- 第六章 - 用語集
- 第七章 - 參考網站
- 第八章 - 關于作者
- 第九章 - 關于譯者