# Promise
https://github.com/javascript-tutorial/zh.javascript.info/blob/master/1-js/11-async/02-promise-basics/article.md
想象一下,你自己是一位頂尖歌手,粉絲沒日沒夜地詢問你下個單曲何時到來。
為了從中解放,你承諾會在單曲發布的第一時間通知他們。你讓粉絲們填寫了他們的個人信息,因此他們會在歌曲發布的第一時間獲取到。即使遇到了不測,歌曲可能永遠不會被發行,他們也會被通知到。
每個人都很開心:你不會被任何人催促;粉絲也不會錯過單曲發行的第一時間。
在編程中,我們經常用現實世界中的事物進行類比:
1. "生產者代碼" 會做一些事情,也需要事件。比如,它加載一個遠程腳本。此時它就像“歌手”。
2. "消費者代碼" 想要在它準備好時知道結果。許多函數都需要結果。此時它們就像是“粉絲”。
3. **promise**是將兩者連接的一個特殊的 JavaScript 對象。就像是“列表”。生產者代碼創建它,然后將它交給每個訂閱的對象,因此它們都可以訂閱到結果。
這種類比并不精確,因為 JavaScipt promises 比簡單的列表更加復雜:它們擁有額外的特性和限制。但是它們仍然有相似之處。
Promise 對象的構造語法是:
~~~js
let promise = new Promise(function(resolve, reject) {
// executor (生產者代碼,"singer")
});
~~~
傳遞給`new Promise`的函數稱之為**executor**。當 promise 被創建時,它會被自動調用。它包含生產者代碼,這最終會產生一個結果。與上文類比,executor 就是“歌手”。
`promise`對象有內部屬性:
* `state`—— 最初是 "pending",然后被改為 "fulfilled" 或 "rejected",
* `result`—— 一個任意值,最初是`undefined`。
當 executor 完成任務時,應調用下列之一:
* `resolve(value)`—— 說明任務已經完成:
* 將`state`設置為`"fulfilled"`,
* sets`result`to`value`。
* `reject(error)`—— 表明有錯誤發生:
* 將`state`設置為`"rejected"`,
* 將`result`設置為`error`。
[](https://github.com/javascript-tutorial/zh.javascript.info/blob/master/1-js/11-async/02-promise-basics/promise-resolve-reject.png)
這是一個簡單的 executor,可以把這一切都聚集在一起:
~~~js
let promise = new Promise(function(resolve, reject) {
// 當 promise 被構造時,函數會自動執行
alert(resolve); // function () { [native code] }
alert(reject); // function () { [native code] }
// 在 1 秒后,結果為“完成!”,表明任務被完成
setTimeout(() => *!*resolve("done!")*/!*, 1000);
});
~~~
我們運行上述代碼后發現兩件事:
1. 會自動并立即調用 executor(通過`new Promise`)。
2. executor 接受兩個參數`resolve`和`reject`—— 這些函數來自于 JavaScipt 引擎。我們不需要創建它們,相反,executor 會在它們準備好時進行調用。
經過一秒鐘的思考后,executor 調用`resolve("done")`來產生結果:
[](https://github.com/javascript-tutorial/zh.javascript.info/blob/master/1-js/11-async/02-promise-basics/promise-resolve-1.png)
這是“任務成功完成”的示例。
現在的是示例則是 promise 的 reject 出現于錯誤的發生:
~~~js
let promise = new Promise(function(resolve, reject) {
// after 1 second signal that the job is finished with an error
setTimeout(() => *!*reject(new Error("Whoops!"))*/!*, 1000);
});
~~~
[](https://github.com/javascript-tutorial/zh.javascript.info/blob/master/1-js/11-async/02-promise-basics/promise-reject-1.png)
總之,executor 應該完成任務(通常會需要時間),然后調用`resolve`或`reject`來改變 promise 對象的對應狀態。
Promise 結果應該是 resolved 或 rejected 的狀態被稱為 "settled",而不是 "pending" 狀態的 promise。
~~~
executor 只會調用 `resolve` 或 `reject`。Promise 的最后狀態一定會變化。
對 `resolve` 和 `reject` 的深層調用都會被忽略:
```js
let promise = new Promise(function(resolve, reject) {
resolve("done");
reject(new Error("…")); // 被忽略
setTimeout(() => resolve("…")); // 被忽略
});
```
executor 所做的任務可能只有一個結果或者一個錯誤。在編程中,還有其他允許 "flowing" 結果的數據結構。例如流和隊列。相對于 promise,它們有著自己的優勢和劣勢。它們不被 JavaScipt 核心支持,而且缺少 promise 所提供的某些語言特性,我們在這里不對 promise 進行過多的討論。
同時,如果我們使用另一個參數調用 `resolve/reject` —— 只有第一個參數會被使用,下一個會被忽略。
~~~
```smart header="Reject with`Error`objects" 從技術上來說,我們可以使用任何類型的參數來調用 `reject`(就像 `resolve`)。但建議在 `reject`(或從它們中繼承)中使用 `Error` 對象。 錯誤原因就會顯示出來。
~~~
````smart header="Resolve/reject can be immediate"
實際上,executor 通常會異步執行一些動作,然后在一段時間后調用 `resolve/reject`,但它不必那么做。我們可以立即調用 `resolve` 或 `reject`,就像這樣:
```js
let promise = new Promise(function(resolve, reject) {
resolve(123); // immediately give the result: 123
});
~~~
比如,當我們開始做一個任務時,它就會發生,然后發現一切都已經被做完了。從技術上來說,這非常好:我們現在有了一個 resolved promise。
~~~
```smart header="The `state` and `result` are internal"
Promise 的 `state` 和 `result` 屬性是內部的。我們不能從代碼中直接訪問它們,但是我們可以使用 `.then/catch` 來訪問,下面是對此的描述。
```
## 消費者:".then" 和 ".catch"
Promise 對象充當生產者(executor)和消費函數之間的連接 —— 那些希望接收結果/錯誤的函數。假設函數可以使用方法 `promise.then` 和 `promise.catch` 進行注冊。
`.then` 的語法:
```js
promise.then(
function(result) { /* handle a successful result */ },
function(error) { /* handle an error */ }
);
```
第一個函數參數在 promise 為 resolved 時被解析,然后得到結果并運行。第二個參數 —— 在狀態為 rejected 并得到錯誤時使用。
例如:
```js run
let promise = new Promise(function(resolve, reject) {
setTimeout(() => resolve("done!"), 1000);
});
// resolve 在 .then 中運行第一個函數
promise.then(
*!*
result => alert(result), // 在 1 秒后顯示“已經完成!”
*/!*
error => alert(error) // 不會運行
);
```
在 rejection 的情況下:
```js run
let promise = new Promise(function(resolve, reject) {
setTimeout(() => reject(new Error("Whoops!")), 1000);
});
// reject 在 .then 中運行第二個函數
promise.then(
result => alert(result), // 無法運行
*!*
error => alert(error) // 在 1 秒后顯示 "Error: Whoops!"
*/!*
);
```
如果我們只對成功完成的情況感興趣,那么我們只為 `.then` 提供一個參數:
```js run
let promise = new Promise(resolve => {
setTimeout(() => resolve("done!"), 1000);
});
*!*
promise.then(alert); // 在 1 秒后顯示 "done!"
*/!*
```
如果我們只對錯誤感興趣,那么我們可以對它使用 `.then(null, function)` 或 "alias":`.catch(function)`
```js run
let promise = new Promise((resolve, reject) => {
setTimeout(() => reject(new Error("Whoops!")), 1000);
});
*!*
// .catch(f) 等同于 promise.then(null, f)
promise.catch(alert); // 在 1 秒后顯示 "Error: Whoops!"
*/!*
```
調用 `.catch(f)` 是 `.then(null, f)` 的模擬,這只是一個簡寫。
````smart header="On settled promises `then` runs immediately"
如果 promise 為 pending 狀態,`.then/catch` 處理器必須要等待結果。相反,如果 promise 已經被處理,它們就會立即執行:
```js run
// 一個立即變成 resolve 的 promise
let promise = new Promise(resolve => resolve("done!"));
promise.then(alert); // 完成!(現在顯示)
```
這對于有時需要時間而且有時要立即完成的任務來說非常方便。確保處理器在兩種情況下都能夠運行。
~~~
````smart header="`.then/catch`的處理器總是異步的" 更確切地說,當 `.then/catch` 處理器應該執行時,它會首先進入內部隊列。JavaScript 引擎從隊列中提取處理器,并在當前代碼完成時執行 `setTimeout(..., 0)`。
換句話說,`.then(handler)`會被觸發,會執行類似于`setTimeout(handler, 0)`的動作。
在下述示例中,promise 被立即 resolved,因此`.then(alert)`被立即觸發:`alert`會進入隊列,在代碼完成之后立即執行。
~~~js
// an immediately resolved promise
let promise = new Promise(resolve => resolve("done!"));
promise.then(alert); // 完成!(在當前代碼完成之后)
alert("code finished"); // 這個 alert 會最先顯示
~~~
因此在`.then`之后的代碼總是在處理器之前被執行(即使實在預先解決 promise 的情況下)。通常這并不重要,只會在特定情況下才會重要。
~~~
我們現在研究一下 promises 如何幫助我們編寫異步代碼的示例。
## 示例:loadScript
我們已經從之前的章節中加載了 `loadScript` 函數。
這是基于回調函數的變體,記住它:
```js
function loadScript(src, callback) {
let script = document.createElement('script');
script.src = src;
script.onload = () => callback(null, script);
script.onerror = () => callback(new Error(`Script load error ` + src));
document.head.append(script);
}
```
我們用 promises 進行重寫。
`loadScript` 新函數不需要請求回調函數,取而代之的是它會創建并返回一個在加載完成時的 promise 對象。外部代碼可以使用 `.then` 向其添加處理器:
```js run
function loadScript(src) {
return new Promise(function(resolve, reject) {
let script = document.createElement('script');
script.src = src;
script.onload = () => resolve(script);
script.onerror = () => reject(new Error("Script load error: " + src));
document.head.append(script);
});
}
```
用法:
```js run
let promise = loadScript("https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.2.0/lodash.js");
promise.then(
script => alert(`${script.src} is loaded!`),
error => alert(`Error: ${error.message}`)
);
promise.then(script => alert('One more handler to do something else!'));
```
我們立刻看到基于回調語法的好處:
```compare minus="Callbacks" plus="Promises"
- 在調用 `loadScript` 時,我們必須已經有了一個 `callback` 函數。換句話說,在調用 `loadScript` **之前**我們必須知道如何處理結果。
- 只能有一個回調。
+ Promises 允許我們按照自然順序進行編碼。首先,我們運行 `loadScript` 和 `.then` 來編寫如何處理結果。
+ 無論何時,只要我們有需要,就可以在 promise 中調用 `.then`。
```
因此,promise 已經為我們的編碼帶來了更好的編碼方式和靈活性。我們會在之后章節看到更多相關內容。
~~~
- 內容介紹
- EcmaScript基礎
- 快速入門
- 常量與變量
- 字符串
- 函數的基本概念
- 條件判斷
- 數組
- 循環
- while循環
- for循環
- 函數基礎
- 對象
- 對象的方法
- 函數
- 變量作用域
- 箭頭函數
- 閉包
- 高階函數
- map/reduce
- filter
- sort
- Promise
- 基本對象
- Arguments 對象
- 剩余參數
- Map和Set
- Json基礎
- RegExp
- Date
- async
- callback
- promise基礎
- promise-api
- promise鏈
- async-await
- 項目實踐
- 標簽系統
- 遠程API請求
- 面向對象編程
- 創建對象
- 原型繼承
- 項目實踐
- Classes
- 構造函數
- extends
- static
- 項目實踐
- 模塊
- import
- export
- 項目實踐
- 第三方擴展庫
- immutable
- Vue快速入門
- 理解MVVM
- Vue中的MVVM模型
- Webpack+Vue快速入門
- 模板語法
- 計算屬性和偵聽器
- Class 與 Style 綁定
- 條件渲染
- 列表渲染
- 事件處理
- 表單輸入綁定
- 組件基礎
- 組件注冊
- Prop
- 自定義事件
- 插槽
- 混入
- 過濾器
- 項目實踐
- 標簽編輯
- iView
- iView快速入門
- 課程講座
- 環境配置
- 第3周 Javascript快速入門