## 37.異步編程的 Promise
> 原文: [http://exploringjs.com/impatient-js/ch_promises.html](http://exploringjs.com/impatient-js/ch_promises.html)
>
> 貢獻者:[iChrisJ](https://github.com/iChrisJ)
在本章中,我們將探索 Promise,另一種交付異步結果的模式。
本章建立在[前一章](ch_async-js.html)的基礎上,以JavaScript異步編程為背景。
### 37.1. 使用 Promise 的基礎知識
Promise 是一種交付異步結果的模式。
#### 37.1.1. 使用基于 Promise 的函數
以下代碼是使用基于 Promise 的函數`addAsync()`的示例(其實現將很快會被展示):
```JavaScript
addAsync(3, 4)
.then(result => { // success
assert.equal(result, 7);
})
.catch(error => { // failure
assert.fail(error);
});
```
Promise 結合了[回調模式](ch_async-js.html#callback-pattern)和[事件模式](ch_async-js.html#event-pattern)的各個方面:
* 與回調模式一樣,Promise 專注于提供一次性結果。
* 與某些事件模式類似,一個基于 Promise 的函數通過返回一個對象( _Promise_ )來傳遞其結果。使用該對象,你可以注冊處理結果(`.then()`)和錯誤(`.catch()`)的回調。
* Promise 的一個獨特之處在于你可以串聯`.then()`和`.catch()`,因為它們都返回 Promise。這有助于順序調用多個異步函數。我們稍后將探討其詳情。
#### 37.1.2. 什么是 Promise?
那么什么是 Promise?有兩種方式來看待它:
* 一方面,它是最終要被交付結果的一個占位符或容器。
* 另一方面,它是一個可以注冊監聽器的對象。
#### 37.1.3. 實現基于 Promise 的函數
這是你如何實現一個基于 Promise 的關于兩個數字`x`和`y`相加的函數:
```JavaScript
function addAsync(x, y) {
return new Promise(
(resolve, reject) => { // (A)
if (x === undefined || y === undefined) {
reject(new Error('Must provide two parameters'));
} else {
resolve(x + y);
}
});
}
```
使用這種返回 Promise 的方式,`addAsync()`立即調用`Promise`構造函數。該函數的實際實現存在于傳遞給該構造函數的回調中(行 A)。該回調被提供了兩個方法:
* `resolve`用于傳遞結果(如果成功)。
* `reject`用于傳遞錯誤(如果失敗)。
#### 37.1.4. Promise 的狀態

圖 21: 一個 Promise 可以處于三種狀態中的任何一種: pending(待定), fulfilled(已履行), 或 rejected(被拒絕). 如果一個 Promise 處于最終 (non-pending 非待定) 狀態, 則稱為 _settled(已結算)_ .
圖 [21](#fig:promise_states_simple) 描述了 Promise 可以進入的三種狀態. Promise 專注于一次性結果并保護您免受 _競態條件_(過早注冊或太晚注冊):
* 如果您過早注冊`.then()`回調或`.catch()`回調,則會在 Promise 結算后被通知。
* 一旦一個 Promise 被結算了,其結算值(結果或錯誤)會被緩存。因此,如果在結算后調用`.then()`或`.catch()`,它們將收到被緩存的值。
此外,一旦 Promise 得到結算,其狀態和結算值就不能再改變了。這有助于使代碼變得可預測,并加強了 Promise 的一次性特性。
接下來,我們將看到更多創建 Promise 的方法。
#### 37.1.5. `Promise.resolve()`:創建一個被給定值履行的 Promise
`Promise.resolve(x)`創建一個使用值`x`實現的 Promise:
```JavaScript
Promise.resolve(123)
.then(x => {
assert.equal(x, 123);
});
```
如果參數已經是一個 Promise,則返回是不變:
```JavaScript
const abcPromise = Promise.resolve('abc');
assert.equal(
Promise.resolve(abcPromise),
abcPromise);
```
因此,給定任意值`x`,您可以使用`Promise.resolve(x)`確保您擁有一個 Promise。
請注意,函數名稱是`resolve`,而不是`fulfill`,因為如果參數是一個被拒絕的 Promise,`.resolve()`會返回被拒絕的 Promise。
#### 37.1.6. `Promise.reject()`:創建一個被給定值拒絕的 Promise
`Promise.reject(err)`創建一個使用值`err`實現的 Promise:
```JavaScript
const myError = new Error('My error!');
Promise.reject(myError)
.catch(err => {
assert.equal(err, myError);
});
```
#### 37.1.7. 在`.then()`回調中返回或拋出
`.then()`處理 Promise 的履行。它返回一個新鮮的 Promise。這個 Promise 是如何達成的取決于回調中發生了什么。我們來看看三種常見情況。
##### 37.1.7.1. 返回一個非 Promise 值
首先,回調可以返回一個非 Promise 值(行 A)。因此,`.then()`返回的 Promise 滿足該值(如 B 行所示):
```JavaScript
Promise.resolve('abc')
.then(str => {
return str + str; // (A)
})
.then(str2 => {
assert.equal(str2, 'abcabc'); // (B)
});
```
##### 37.1.7.2. 返回一個 Promise
其次,回調可以返回一個 Promise `p`(行 A)。因此,`p`“成為”`.then()`返回的內容(`.then()`已經返回的 Promise 實際上被`p`替換)。
```JavaScript
Promise.resolve('abc')
.then(str => {
return Promise.resolve(123); // (A)
})
.then(num => {
assert.equal(num, 123);
});
```
##### 37.1.7.3. 拋出異常
第三,回調可以拋出異常。因此,`.then()`返回的 Promise 被該異常拒絕。也就是說,同步錯誤被轉換為異步錯誤。
```JavaScript
const myError = new Error('My error!');
Promise.resolve('abc')
.then(str => {
throw myError;
})
.catch(err => {
assert.equal(err, myError);
});
```
#### 37.1.8. `.catch()`及其回調
`.then()`和`.catch()`之間的唯一區別是后者是由拒絕而不是履行觸發的。但是,這兩種方法都以相同的方式將其回調的操作轉換為 Promise。例如,在以下代碼中,行 A 中`.catch()`回調返回的值將成為履行值:
```JavaScript
const err = new Error();
Promise.reject(err)
.catch(e => {
assert.equal(e, err);
// Something went wrong, use a default value
return 'default value'; // (A)
})
.then(str => {
assert.equal(str, 'default value');
});
```
#### 37.1.9. 串聯方法調用
由于`.then()`和`.catch()`總是返回 Promise,您可以創建任意長度的方法調用鏈:
```JavaScript
function myAsyncFunc() {
return asyncFunc1()
.then(result1 => {
// ···
return asyncFunc2(); // a Promise
})
.then(result2 => {
// ···
return result2 || '(Empty)'; // not a Promise
})
.then(result3 => {
// ···
return asyncFunc4(); // a Promise
});
}
```
在某種程度上,`.then()`是同步分號的異步版本:
* `.then()`是按順序執行兩個異步操作。
* 分號是按順序執行兩個同步操作。
您還可以將`.catch()`添加到混合的方法調用鏈中,并讓它同時處理多個錯誤源:
```JavaScript
asyncFunc1()
.then(result1 => {
// ···
return asyncFunction2();
})
.then(result2 => {
// ···
})
.catch(error => {
// Failure: handle errors of asyncFunc1(), asyncFunc2()
// and any (sync) exceptions thrown in previous callbacks
});
```
#### 37.1.10. Promise 的優勢
在處理一次性結果時,這些是 Promise 優于普通回調的一些優點:
* 基于 Promise 的函數和方法的類型簽名更清晰:如果函數是基于回調的,則某些參數是關于輸入,而最后的一個或兩個回調是關于輸出。使用 Promise,與輸出相關的所有內容都通過返回值處理。
* 鏈式異步處理步驟更方便。
* 錯誤處理可以負責同步和異步錯誤。
* 編寫基于 Promise 的函數稍微容易一些,因為您可以使用一些調用函數和處理結果的同步工具(例如`.map()`)。我們將在本章末尾看到一個例子。
* Promise 是一個單一的標準,正逐漸取代幾個互不相容的替代方案。例如,在 Node.js 中,許多函數現在都可以在基于 Promise 的版本中可用。新的異步瀏覽器 API 通常是基于 Promise 的。
Promise 最大的優勢之一就是不直接使用它們:它們是 _異步函數_ 的基礎,一種用于執行異步計算的同步語法。異步函數將在下一章中介紹。
### 37.2. 范例
看到它們的使用有助于理解 Promise。我們來看看例子。
#### 37.2.1. Node.js:異步讀取文件
考慮以下帶有 JSON 數據的文本文件`person.json`:
```JSON
{
"first": "Jane",
"last": "Doe"
}
```
讓我們看看兩個版本的代碼,它們讀取這個文件并將其解析為一個對象。首先,基于回調的版本。第二,基于 Promise 的版本。
##### 37.2.1.1. 基于回調的版本
以下代碼讀取此文件的內容并將其轉換為 JavaScript 對象。它基于 Node.js 風格的回調:
```JavaScript
import * as fs from 'fs';
fs.readFile('person.json',
(error, text) => {
if (error) { // (A)
// Failure
assert.fail(error);
} else {
// Success
try { // (B)
const obj = JSON.parse(text); // (C)
assert.deepEqual(obj, {
first: 'Jane',
last: 'Doe',
});
} catch (e) {
// Invalid JSON
assert.fail(e);
}
}
});
```
`fs`是用于文件系統操作的 Node.js 內置模塊。我們使用基于回調的函數`fs.readFile()`來讀取名稱為`person.json`的文件。如果我們成功,內容將通過參數`text`作為字符串傳遞。在 C 行中,我們將該字符串從基于文本的數據格式 _JSON_ 轉換為 JavaScript 對象。 `JSON`是 JavaScript 標準庫的一部分。
請注意,有兩種錯誤處理機制:行 A 中的`if`負責`fs.readFile()`報告的異步錯誤,而行 B 中的`try`負責`JSON.parse()`報告的同步錯誤。
##### 37.2.1.2. 基于 Promise 的版本
以下代碼使用`readFileAsync()`,一個基于 Promise 的`fs.readFile()`版本(通過`util.promisify()`創建,稍后會解釋):
```JavaScript
readFileAsync('person.json')
.then(text => { // (A)
// Success
const obj = JSON.parse(text);
assert.deepEqual(obj, {
first: 'Jane',
last: 'Doe',
});
})
.catch(err => { // (B)
// Failure: file I/O error or JSON syntax error
assert.fail(err);
});
```
函數`readFileAsync()`返回一個 Promise。在行 A 中,我們通過 Promise 的方法`.then()`指定成功的回調。 `then`回調中的剩余代碼是同步的。
`.then()`返回一個 Promise,它允許在 B 行調用 Promise 方法`.catch()`。我們用它來指定一個失敗的回調。
請注意,`.catch()`允許我們處理`readFileAsync()`的異步錯誤和`JSON.parse()`的同步錯誤。我們稍后會看到它究竟是如何工作的。
#### 37.2.2. 瀏覽器:Promisifying(Promise化) `XMLHttpRequest`
我們以前見過基于事件的`XMLHttpRequest` API,用于在 Web 瀏覽器中下載數據。以下函數將Promise化 API:
```JavaScript
function httpGet(url) {
return new Promise(
(resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.onload = () => {
if (xhr.status === 200) {
resolve(xhr.responseText); // (A)
} else {
// Something went wrong (404 etc.)
reject(new Error(xhr.statusText)); // (B)
}
}
xhr.onerror = () => {
reject(new Error('Network error')); // (C)
};
xhr.open('GET', url);
xhr.send();
});
}
```
注意如何通過`resolve()`和`reject()`處理`XMLHttpRequest`的結果:
* 一個成功的結果導致返回的 Promise 得以實現(行 A)。
* 錯誤導致 Promise 被拒絕(B 行和 C 行)。
這是您使用`httpGet()`的方式:
```JavaScript
httpGet('http://example.com/textfile.txt')
.then(content => {
assert.equal(content, 'Content of textfile.txt\n');
})
.catch(error => {
assert.fail(error);
});
```
 **練習:找出 Promise**
`exercises/promises/promise_timeout_test.js`
#### 37.2.3. Node.js:`util.promisify()`
`util.promisify()`是一個實用程序函數,它將基于回調的函數`f`轉換為基于 Promise 的函數`f`。也就是說,我們將從這種類型的簽名:
```JavaScript
f(arg_1, ···, arg_n, (err: Error, result: T) => void) : void
```
對于這種類型的簽名:
```JavaScript
f(arg_1, ···, arg_n) : Promise<T>
```
以下代碼Promise化了基于回調的`fs.readFile()`(行 A)并使用它:
```JavaScript
import * as fs from 'fs';
import {promisify} from 'util';
const readFileAsync = promisify(fs.readFile); // (A)
readFileAsync('some-file.txt', {encoding: 'utf8'})
.then(text => {
assert.equal(text, 'The content of some-file.txt\n');
})
.catch(err => {
assert.fail(err);
});
```
 **練習:`util.promisify()`**
* 使用`util.promisify()`:`exercises/promises/read_file_async_exrc.js`
* 自己實現`util.promisify()`:`exercises/promises/my_promisify_test.js`
#### 37.2.4. 瀏覽器:Fetch API
所有現代瀏覽器都支持 Fetch,這是一種基于 Promise 的新 API,用于下載數據。可以把它想象成`XMLHttpRequest`的基于 Promise 的版本。以下是 [API](https://fetch.spec.whatwg.org/#fetch-api) 的一段摘錄:
```JavaScript
interface Body {
text() : Promise<string>;
···
}
interface Response extends Body {
···
}
declare function fetch(str) : Promise<Response>;
```
這意味著,你可以使用`fetch()`如下:
```JavaScript
fetch('http://example.com/textfile.txt')
.then(response => response.text())
.then(text => {
assert.equal(text, 'Content of textfile.txt\n');
});
```
 **練習:使用 fetch API**
`exercises/promises/fetch_json_test.js`
### 37.3. 錯誤處理:不要混淆拒絕和異常
異步代碼中錯誤處理的一般規則是:
> 不要混淆(異步)拒絕和(同步)異常
理由是,如果您可以使用單一的錯誤處理機制,那么您的代碼就不那么冗余了。
唉,很容易意外地打破這個規則。例如:
```JavaScript
// Don't do this
function asyncFunc() {
doSomethingSync(); // (A)
return doSomethingAsync()
.then(result => {
// ···
});
}
```
問題是,如果在 行 A 拋出異常,那么`asyncFunc()`將拋出異常。該函數的調用者只會期待拒絕,并且不會為異常做好準備。我們可以通過三種方式解決此問題。
我們可以在`try-catch`語句中包裝函數的整個主體,并在拋出異常時返回被拒絕的 Promise:
```JavaScript
// Solution 1
function asyncFunc() {
try {
doSomethingSync();
return doSomethingAsync()
.then(result => {
// ···
});
} catch (err) {
return Promise.reject(err);
}
}
```
鑒于`.then()`將異常轉換為拒絕,我們可以在`.then()`回調中執行`doSomethingSync()`。為此,我們通過`Promise.resolve()`啟動 Promise 鏈。我們忽略了最初的 Promise 的履行值`undefined`。
```JavaScript
// Solution 2
function asyncFunc() {
return Promise.resolve()
.then(() => {
doSomethingSync();
return doSomethingAsync();
})
.then(result => {
// ···
});
}
```
最后,`new Promise()`還將異常轉換為拒絕。因此,使用此構造函數與以前的解決方案類似:
```JavaScript
// Solution 3
function asyncFunc() {
return new Promise((resolve, reject) => {
doSomethingSync();
resolve(doSomethingAsync());
})
.then(result => {
// ···
});
}
```
### 37.4. 基于 Promise 的函數同步啟動,異步解決
大多數基于 Promise 的函數執行如下:
* 他們的執行立即開始,同步。
* 但他們返回的 Promise 保證可以異步結算(如果有的話)。
以下代碼演示了:
```JavaScript
function asyncFunc() {
console.log('asyncFunc');
return new Promise(
(resolve, _reject) => {
console.log('Callback of new Promise()');
resolve();
});
}
console.log('Start');
asyncFunc()
.then(() => {
console.log('Callback of .then()'); // (A)
});
console.log('End');
// Output:
// 'Start'
// 'asyncFunc'
// 'Callback of new Promise()'
// 'End'
// 'Callback of .then()'
```
我們可以看到`new Promise()`的回調在代碼結束之前執行,而結果在稍后傳遞(行 A)。
這意味著您的代碼可以依賴于運行到完成語義(如[前一章](ch_async-js.html)中所述),并且串聯的 Promise 不會匱乏其他任務的處理時間。
此外,此規則導致基于 Promise 的函數前后一致的異步地返回結果。不是有時立即,有時異步的。這種可預測性使代碼更易于使用。有關更多信息,請參閱 Isaac Z. Schlueter 的[“異步設計 API”](http://blog.izs.me/post/59142742143/designing-apis-for-asynchrony)。
### 37.5. `Promise.all()`:并發和 Promise 數組
#### 37.5.1. 順序執行與并發執行
請考慮以下代碼:
```JavaScript
const asyncFunc1 = () => Promise.resolve('one');
const asyncFunc2 = () => Promise.resolve('two');
asyncFunc1()
.then(result1 => {
assert.equal(result1, 'one');
return asyncFunc2();
})
.then(result2 => {
assert.equal(result2, 'two');
});
```
使用`.then()`,基于 Promise 的函數始終順序地執行:僅在`asyncFunc1()`的結果確定后,才會執行`asyncFunc2()`。
相反,輔助函數`Promise.all()`以更多并發的方式執行基于 Promise 的函數:
```JavaScript
Promise.all([asyncFunc1(), asyncFunc2()])
.then(arr => {
assert.deepEqual(arr, ['one', 'two']);
});
```
它的類型簽名是:
```JavaScript
Promise.all<T>(promises: Iterable<Promise<T>>): Promise<T[]>
```
參數`promises`是 Promise 的可迭代參數。結果是單個 Promise,其結算方式如下:
* 如果滿足所有輸入 Promise,則輸出 Promise 將通過一個履行值數組來實現。
* 如果至少有一個輸入 Promise 被拒絕,則輸出 Promise 將被拒絕,并帶有輸入 Promise 的拒絕值。
換句話說:你從一個可變的 Promise 轉到一個 Array 的 Promise。
#### 37.5.2. 并發提示:關注計算何時開始
確定“并發”異步代碼的方法的提示:關注異步計算何時開始,而不是如何處理 Promise。例如,以下使用`.then()`的代碼與使用`Promise.all()`的版本一樣“并發”:
```JavaScript
const promise1 = asyncFunc1();
const promise2 = asyncFunc2();
promise1
.then(result1 => {
assert.equal(result1, 'one');
return promise2;
})
.then(result2 => {
assert.equal(result2, 'two');
});
```
`asyncFunc1()`和`asyncFunc2()`大致同時開始。一旦兩個 Promise 都滿足,兩個`.then()`調用幾乎立即執行。如果首先滿足`promise1`,這種方法甚至比使用`Promise.all()`(等待所有 Promise 都滿足)更快。
#### 37.5.3. `Promise.all()`是 fork-join
`Promise.all()`與并發模式“fork join”松散相關。例如:
```JavaScript
Promise.all([
// Fork async computations
httpGet('http://example.com/file1.txt'),
httpGet('http://example.com/file2.txt'),
])
// Join async computations
.then(([text1, text2]) => {
assert.equal(text1, 'Content of file1.txt\n');
assert.equal(text2, 'Content of file2.txt\n');
});
```
#### 37.5.4。通過`Promise.all()`異步`.map()`
諸如`.map()`,`.filter()`等的數組變換方法用于同步計算。例如:
```JavaScript
function timesTwoSync(x) {
return 2 * x;
}
const arr = [1, 2, 3];
const result = arr.map(timesTwoSync);
assert.deepEqual(result, [2, 4, 6]);
```
`.map()`的回調是否可能是基于 Promise 的函數?是的,如果你使用`Promise.all()`將一個 Promise 數組轉換為一個(履行)值數組:
```JavaScript
function timesTwoAsync(x) {
return new Promise(resolve => resolve(x * 2));
}
const arr = [1, 2, 3];
const promiseArr = arr.map(timesTwoAsync);
Promise.all(promiseArr)
.then(result => {
assert.deepEqual(result, [2, 4, 6]);
});
```
##### 37.5.4.1。一個更現實的例子
下面的代碼是一個更現實的例子:我們使用`.map()`將示例從 fork-join 部分轉換為一個函數,其參數是一個帶有要下載的文本文件 URL 的 Array。
```JavaScript
function downloadTexts(fileUrls) {
const promisedTexts = fileUrls.map(httpGet);
return Promise.all(promisedTexts);
}
downloadTexts([
'http://example.com/file1.txt',
'http://example.com/file2.txt',
])
.then(texts => {
assert.deepEqual(
texts, [
'Content of file1.txt\n',
'Content of file2.txt\n',
]);
});
```
 **練習:`Promise.all()`和列表文件**
`exercises/promises/list_files_async_test.js`
### 37.6. 串聯 Promise 的提示
本節提供了串聯 Promise 的提示。
#### 37.6.1. 串聯錯誤:失去尾巴
問題:
```JavaScript
// Don't do this
function foo() {
const promise = asyncFunc();
promise.then(result => {
// ···
});
return promise;
}
```
計算從`asyncFunc()`返回的 Promise 開始。但之后,計算繼續,并通過`.then()`創建另一個 Promise。 `foo()`返回前 Promise,但應返回后者。這是如何解決它:
```JavaScript
function foo() {
const promise = asyncFunc();
return promise.then(result => {
// ···
});
}
```
#### 37.6.2. 串聯錯誤:嵌套
問題:
```JavaScript
// Don't do this
asyncFunc1()
.then(result1 => {
return asyncFunc2()
.then(result2 => { // (A)
// ···
});
});
```
行 A 中的`.then()`是嵌套的。扁平結構會更好:
```JavaScript
asyncFunc1()
.then(result1 => {
return asyncFunc2();
})
.then(result2 => {
// ···
});
```
#### 37.6.3. 串聯錯誤:多余的嵌套
這是可避免嵌套的另一個例子:
```JavaScript
// Don't do this
asyncFunc1()
.then(result1 => {
if (result1 < 0) {
return asyncFuncA()
.then(resultA => 'Result: ' + resultA);
} else {
return asyncFuncB()
.then(resultB => 'Result: ' + resultB);
}
});
```
我們可以再次獲得扁平結構:
```JavaScript
asyncFunc1()
.then(result1 => {
return result1 < 0 ? asyncFuncA() : asyncFuncB();
})
.then(resultAB => {
return 'Result: ' + resultAB;
});
```
#### 37.6.4. 嵌套本身并不邪惡
在以下代碼中,我們受益于 _不是_ 嵌套:
```JavaScript
db.open()
.then(connection => { // (A)
return connection.select({ name: 'Jane' })
.then(result => { // (B)
// Process result
// Use `connection` to make more queries
})
// ···
.catch(error => {
// handle errors
})
.finally(() => {
connection.close(); // (C)
});
})
```
我們在 行 A 接收異步結果。在 B 行中,我們正在嵌套,因此我們可以在回調內和 C 行中訪問變量`connection`。
#### 37.6.5. 串聯錯誤:創建 Promise 而不是串聯
問題:
```JavaScript
// Don't do this
class Model {
insertInto(db) {
return new Promise((resolve, reject) => { // (A)
db.insert(this.fields)
.then(resultCode => {
this.notifyObservers({event: 'created', model: this});
resolve(resultCode);
}).catch(err => {
reject(err);
})
});
}
// ···
}
```
在 行 A 中,我們創建了一個 Promise 來提供`db.insert()`的結果。這是不必要的冗長,可以簡化:
```JavaScript
class Model {
insertInto(db) {
return db.insert(this.fields)
.then(resultCode => {
this.notifyObservers({event: 'created', model: this});
return resultCode;
});
}
// ···
}
```
關鍵的想法是我們不需要創造一個 Promise;我們可以返回`.then()`調用的結果。另一個好處是我們不需要捕獲并重新拒絕`db.insert()`的失敗。我們只是將其拒絕傳遞給`.insertInto()`的調用者。
### 37.7. 進一步閱讀
* [“探索 ES6”](http://exploringjs.com/es6/ch_promises.html)深入了解 Promise,包括它們的實現方式。
- I.背景
- 1.關于本書(ES2019 版)
- 2.常見問題:本書
- 3. JavaScript 的歷史和演變
- 4.常見問題:JavaScript
- II.第一步
- 5.概覽
- 6.語法
- 7.在控制臺上打印信息(console.*)
- 8.斷言 API
- 9.測驗和練習入門
- III.變量和值
- 10.變量和賦值
- 11.值
- 12.運算符
- IV.原始值
- 13.非值undefined和null
- 14.布爾值
- 15.數字
- 16. Math
- 17. Unicode - 簡要介紹(高級)
- 18.字符串
- 19.使用模板字面值和標記模板
- 20.符號
- V.控制流和數據流
- 21.控制流語句
- 22.異常處理
- 23.可調用值
- VI.模塊化
- 24.模塊
- 25.單個對象
- 26.原型鏈和類
- 七.集合
- 27.同步迭代
- 28.數組(Array)
- 29.類型化數組:處理二進制數據(高級)
- 30.映射(Map)
- 31. WeakMaps(WeakMap)
- 32.集(Set)
- 33. WeakSets(WeakSet)
- 34.解構
- 35.同步生成器(高級)
- 八.異步
- 36. JavaScript 中的異步編程
- 37.異步編程的 Promise
- 38.異步函數
- IX.更多標準庫
- 39.正則表達式(RegExp)
- 40.日期(Date)
- 41.創建和解析 JSON(JSON)
- 42.其余章節在哪里?