mirror of
https://github.com/ruanyf/es6tutorial.git
synced 2025-05-24 18:32:22 +00:00
commit
c63caa345d
@ -546,7 +546,7 @@ function ArrayOf(){
|
||||
|
||||
## 数组实例的 copyWithin()
|
||||
|
||||
数组实例的`copyWithin`方法,在当前数组内部,将指定位置的成员复制到其他位置(会覆盖原有成员),然后返回当前数组。也就是说,使用这个方法,会修改当前数组。
|
||||
数组实例的`copyWithin()`方法,在当前数组内部,将指定位置的成员复制到其他位置(会覆盖原有成员),然后返回当前数组。也就是说,使用这个方法,会修改当前数组。
|
||||
|
||||
```javascript
|
||||
Array.prototype.copyWithin(target, start = 0, end = this.length)
|
||||
@ -555,8 +555,8 @@ Array.prototype.copyWithin(target, start = 0, end = this.length)
|
||||
它接受三个参数。
|
||||
|
||||
- target(必需):从该位置开始替换数据。如果为负值,表示倒数。
|
||||
- start(可选):从该位置开始读取数据,默认为 0。如果为负值,表示倒数。
|
||||
- end(可选):到该位置前停止读取数据,默认等于数组长度。如果为负值,表示倒数。
|
||||
- start(可选):从该位置开始读取数据,默认为 0。如果为负值,表示从末尾开始计算。
|
||||
- end(可选):到该位置前停止读取数据,默认等于数组长度。如果为负值,表示从末尾开始计算。
|
||||
|
||||
这三个参数都应该是数值,如果不是,会自动转为数值。
|
||||
|
||||
|
@ -34,11 +34,11 @@
|
||||
|
||||
很多浏览器操作的 API,用到了二进制数组操作二进制数据,下面是其中的几个。
|
||||
|
||||
- File API
|
||||
- XMLHttpRequest
|
||||
- Fetch API
|
||||
- Canvas
|
||||
- WebSockets
|
||||
- [Canvas](#canvas)
|
||||
- [Fetch API](#fetch-api)
|
||||
- [File API](#file-api)
|
||||
- [WebSockets](#websocket)
|
||||
- [XMLHttpRequest](#ajax)
|
||||
|
||||
## ArrayBuffer 对象
|
||||
|
||||
@ -448,37 +448,42 @@ Float64Array.BYTES_PER_ELEMENT // 8
|
||||
|
||||
### ArrayBuffer 与字符串的互相转换
|
||||
|
||||
`ArrayBuffer`转为字符串,或者字符串转为`ArrayBuffer`,有一个前提,即字符串的编码方法是确定的。假定字符串采用 UTF-16 编码(JavaScript 的内部编码方式),可以自己编写转换函数。
|
||||
`ArrayBuffer` 和字符串的相互转换,使用原生 `TextEncoder` 和 `TextDecoder` 方法。为了便于说明用法,下面的代码都按照 TypeScript 的用法,给出了类型签名。
|
||||
|
||||
```javascript
|
||||
// ArrayBuffer 转为字符串,参数为 ArrayBuffer 对象
|
||||
function ab2str(buf) {
|
||||
// 注意,如果是大型二进制数组,为了避免溢出,
|
||||
// 必须一个一个字符地转
|
||||
if (buf && buf.byteLength < 1024) {
|
||||
return String.fromCharCode.apply(null, new Uint16Array(buf));
|
||||
}
|
||||
|
||||
const bufView = new Uint16Array(buf);
|
||||
const len = bufView.length;
|
||||
const bstr = new Array(len);
|
||||
for (let i = 0; i < len; i++) {
|
||||
bstr[i] = String.fromCharCode.call(null, bufView[i]);
|
||||
}
|
||||
return bstr.join('');
|
||||
/**
|
||||
* Convert ArrayBuffer/TypedArray to String via TextDecoder
|
||||
*
|
||||
* @see https://developer.mozilla.org/en-US/docs/Web/API/TextDecoder
|
||||
*/
|
||||
function ab2str(
|
||||
input: ArrayBuffer | Uint8Array | Int8Array | Uint16Array | Int16Array | Uint32Array | Int32Array,
|
||||
outputEncoding: string = 'utf8',
|
||||
): string {
|
||||
const decoder = new TextDecoder(outputEncoding)
|
||||
return decoder.decode(input)
|
||||
}
|
||||
|
||||
// 字符串转为 ArrayBuffer 对象,参数为字符串
|
||||
function str2ab(str) {
|
||||
const buf = new ArrayBuffer(str.length * 2); // 每个字符占用2个字节
|
||||
const bufView = new Uint16Array(buf);
|
||||
for (let i = 0, strLen = str.length; i < strLen; i++) {
|
||||
bufView[i] = str.charCodeAt(i);
|
||||
}
|
||||
return buf;
|
||||
/**
|
||||
* Convert String to ArrayBuffer via TextEncoder
|
||||
*
|
||||
* @see https://developer.mozilla.org/zh-CN/docs/Web/API/TextEncoder
|
||||
*/
|
||||
function str2ab(input: string): ArrayBuffer {
|
||||
const view = str2Uint8Array(input)
|
||||
return view.buffer
|
||||
}
|
||||
|
||||
/** Convert String to Uint8Array */
|
||||
function str2Uint8Array(input: string): Uint8Array {
|
||||
const encoder = new TextEncoder()
|
||||
const view = encoder.encode(input)
|
||||
return view
|
||||
}
|
||||
```
|
||||
|
||||
上面代码中,`ab2str()`的第二个参数`outputEncoding`给出了输出编码的编码,一般保持默认值(`utf-8`),其他可选值参见[官方文档](https://encoding.spec.whatwg.org)或 [Node.js 文档](https://nodejs.org/api/util.html#util_whatwg_supported_encodings)。
|
||||
|
||||
### 溢出
|
||||
|
||||
不同的视图类型,所能容纳的数值范围是确定的。超出这个范围,就会出现溢出。比如,8 位视图只能容纳一个 8 位的二进制值,如果放入一个 9 位的值,就会溢出。
|
||||
@ -574,7 +579,7 @@ v3.byteOffset // 2
|
||||
|
||||
### TypedArray.prototype.length
|
||||
|
||||
`length`属性表示 TypedArray 数组含有多少个成员。注意将`byteLength`属性和`length`属性区分,前者是字节长度,后者是成员长度。
|
||||
`length`属性表示 `TypedArray` 数组含有多少个成员。注意将 `length` 属性和 `byteLength` 属性区分,前者是成员长度,后者是字节长度。
|
||||
|
||||
```javascript
|
||||
const a = new Int16Array(8);
|
||||
|
475
docs/async-iterator.md
Normal file
475
docs/async-iterator.md
Normal file
@ -0,0 +1,475 @@
|
||||
# 异步遍历器
|
||||
|
||||
## 同步遍历器的问题
|
||||
|
||||
《遍历器》一章说过,Iterator 接口是一种数据遍历的协议,只要调用遍历器对象的`next`方法,就会得到一个对象,表示当前遍历指针所在的那个位置的信息。`next`方法返回的对象的结构是`{value, done}`,其中`value`表示当前的数据的值,`done`是一个布尔值,表示遍历是否结束。
|
||||
|
||||
```javascript
|
||||
function idMaker() {
|
||||
let index = 0;
|
||||
|
||||
return {
|
||||
next: function() {
|
||||
return { value: index++, done: false };
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const it = idMaker();
|
||||
|
||||
it.next().value // 0
|
||||
it.next().value // 1
|
||||
it.next().value // 2
|
||||
// ...
|
||||
```
|
||||
|
||||
上面代码中,变量`it`是一个遍历器(iterator)。每次调用`it.next()`方法,就返回一个对象,表示当前遍历位置的信息。
|
||||
|
||||
这里隐含着一个规定,`it.next()`方法必须是同步的,只要调用就必须立刻返回值。也就是说,一旦执行`it.next()`方法,就必须同步地得到`value`和`done`这两个属性。如果遍历指针正好指向同步操作,当然没有问题,但对于异步操作,就不太合适了。
|
||||
|
||||
```javascript
|
||||
function idMaker() {
|
||||
let index = 0;
|
||||
|
||||
return {
|
||||
next: function() {
|
||||
return new Promise(function (resolve, reject) {
|
||||
setTimeout(() => {
|
||||
resolve({ value: index++, done: false });
|
||||
}, 1000);
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
上面代码中,`next()`方法返回的是一个 Promise 对象,这样就不行,不符合 Iterator 协议,只要代码里面包含异步操作都不行。也就是说,Iterator 协议里面`next()`方法只能包含同步操作。
|
||||
|
||||
目前的解决方法是,将异步操作包装成 Thunk 函数或者 Promise 对象,即`next()`方法返回值的`value`属性是一个 Thunk 函数或者 Promise 对象,等待以后返回真正的值,而`done`属性则还是同步产生的。
|
||||
|
||||
```javascript
|
||||
function idMaker() {
|
||||
let index = 0;
|
||||
|
||||
return {
|
||||
next: function() {
|
||||
return {
|
||||
value: new Promise(resolve => setTimeout(() => resolve(index++), 1000)),
|
||||
done: false
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const it = idMaker();
|
||||
|
||||
it.next().value.then(o => console.log(o)) // 1
|
||||
it.next().value.then(o => console.log(o)) // 2
|
||||
it.next().value.then(o => console.log(o)) // 3
|
||||
// ...
|
||||
```
|
||||
|
||||
上面代码中,`value`属性的返回值是一个 Promise 对象,用来放置异步操作。但是这样写很麻烦,不太符合直觉,语义也比较绕。
|
||||
|
||||
ES2018 [引入](https://github.com/tc39/proposal-async-iteration)了“异步遍历器”(Async Iterator),为异步操作提供原生的遍历器接口,即`value`和`done`这两个属性都是异步产生。
|
||||
|
||||
## 异步遍历的接口
|
||||
|
||||
异步遍历器的最大的语法特点,就是调用遍历器的`next`方法,返回的是一个 Promise 对象。
|
||||
|
||||
```javascript
|
||||
asyncIterator
|
||||
.next()
|
||||
.then(
|
||||
({ value, done }) => /* ... */
|
||||
);
|
||||
```
|
||||
|
||||
上面代码中,`asyncIterator`是一个异步遍历器,调用`next`方法以后,返回一个 Promise 对象。因此,可以使用`then`方法指定,这个 Promise 对象的状态变为`resolve`以后的回调函数。回调函数的参数,则是一个具有`value`和`done`两个属性的对象,这个跟同步遍历器是一样的。
|
||||
|
||||
我们知道,一个对象的同步遍历器的接口,部署在`Symbol.iterator`属性上面。同样地,对象的异步遍历器接口,部署在`Symbol.asyncIterator`属性上面。不管是什么样的对象,只要它的`Symbol.asyncIterator`属性有值,就表示应该对它进行异步遍历。
|
||||
|
||||
下面是一个异步遍历器的例子。
|
||||
|
||||
```javascript
|
||||
const asyncIterable = createAsyncIterable(['a', 'b']);
|
||||
const asyncIterator = asyncIterable[Symbol.asyncIterator]();
|
||||
|
||||
asyncIterator
|
||||
.next()
|
||||
.then(iterResult1 => {
|
||||
console.log(iterResult1); // { value: 'a', done: false }
|
||||
return asyncIterator.next();
|
||||
})
|
||||
.then(iterResult2 => {
|
||||
console.log(iterResult2); // { value: 'b', done: false }
|
||||
return asyncIterator.next();
|
||||
})
|
||||
.then(iterResult3 => {
|
||||
console.log(iterResult3); // { value: undefined, done: true }
|
||||
});
|
||||
```
|
||||
|
||||
上面代码中,异步遍历器其实返回了两次值。第一次调用的时候,返回一个 Promise 对象;等到 Promise 对象`resolve`了,再返回一个表示当前数据成员信息的对象。这就是说,异步遍历器与同步遍历器最终行为是一致的,只是会先返回 Promise 对象,作为中介。
|
||||
|
||||
由于异步遍历器的`next`方法,返回的是一个 Promise 对象。因此,可以把它放在`await`命令后面。
|
||||
|
||||
```javascript
|
||||
async function f() {
|
||||
const asyncIterable = createAsyncIterable(['a', 'b']);
|
||||
const asyncIterator = asyncIterable[Symbol.asyncIterator]();
|
||||
console.log(await asyncIterator.next());
|
||||
// { value: 'a', done: false }
|
||||
console.log(await asyncIterator.next());
|
||||
// { value: 'b', done: false }
|
||||
console.log(await asyncIterator.next());
|
||||
// { value: undefined, done: true }
|
||||
}
|
||||
```
|
||||
|
||||
上面代码中,`next`方法用`await`处理以后,就不必使用`then`方法了。整个流程已经很接近同步处理了。
|
||||
|
||||
注意,异步遍历器的`next`方法是可以连续调用的,不必等到上一步产生的 Promise 对象`resolve`以后再调用。这种情况下,`next`方法会累积起来,自动按照每一步的顺序运行下去。下面是一个例子,把所有的`next`方法放在`Promise.all`方法里面。
|
||||
|
||||
```javascript
|
||||
const asyncIterable = createAsyncIterable(['a', 'b']);
|
||||
const asyncIterator = asyncIterable[Symbol.asyncIterator]();
|
||||
const [{value: v1}, {value: v2}] = await Promise.all([
|
||||
asyncIterator.next(), asyncIterator.next()
|
||||
]);
|
||||
|
||||
console.log(v1, v2); // a b
|
||||
```
|
||||
|
||||
另一种用法是一次性调用所有的`next`方法,然后`await`最后一步操作。
|
||||
|
||||
```javascript
|
||||
async function runner() {
|
||||
const writer = openFile('someFile.txt');
|
||||
writer.next('hello');
|
||||
writer.next('world');
|
||||
await writer.return();
|
||||
}
|
||||
|
||||
runner();
|
||||
```
|
||||
|
||||
## for await...of
|
||||
|
||||
前面介绍过,`for...of`循环用于遍历同步的 Iterator 接口。新引入的`for await...of`循环,则是用于遍历异步的 Iterator 接口。
|
||||
|
||||
```javascript
|
||||
async function f() {
|
||||
for await (const x of createAsyncIterable(['a', 'b'])) {
|
||||
console.log(x);
|
||||
}
|
||||
}
|
||||
// a
|
||||
// b
|
||||
```
|
||||
|
||||
上面代码中,`createAsyncIterable()`返回一个拥有异步遍历器接口的对象,`for...of`循环自动调用这个对象的异步遍历器的`next`方法,会得到一个 Promise 对象。`await`用来处理这个 Promise 对象,一旦`resolve`,就把得到的值(`x`)传入`for...of`的循环体。
|
||||
|
||||
`for await...of`循环的一个用途,是部署了 asyncIterable 操作的异步接口,可以直接放入这个循环。
|
||||
|
||||
```javascript
|
||||
let body = '';
|
||||
|
||||
async function f() {
|
||||
for await(const data of req) body += data;
|
||||
const parsed = JSON.parse(body);
|
||||
console.log('got', parsed);
|
||||
}
|
||||
```
|
||||
|
||||
上面代码中,`req`是一个 asyncIterable 对象,用来异步读取数据。可以看到,使用`for await...of`循环以后,代码会非常简洁。
|
||||
|
||||
如果`next`方法返回的 Promise 对象被`reject`,`for await...of`就会报错,要用`try...catch`捕捉。
|
||||
|
||||
```javascript
|
||||
async function () {
|
||||
try {
|
||||
for await (const x of createRejectingIterable()) {
|
||||
console.log(x);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
注意,`for await...of`循环也可以用于同步遍历器。
|
||||
|
||||
```javascript
|
||||
(async function () {
|
||||
for await (const x of ['a', 'b']) {
|
||||
console.log(x);
|
||||
}
|
||||
})();
|
||||
// a
|
||||
// b
|
||||
```
|
||||
|
||||
Node v10 支持异步遍历器,Stream 就部署了这个接口。下面是读取文件的传统写法与异步遍历器写法的差异。
|
||||
|
||||
```javascript
|
||||
// 传统写法
|
||||
function main(inputFilePath) {
|
||||
const readStream = fs.createReadStream(
|
||||
inputFilePath,
|
||||
{ encoding: 'utf8', highWaterMark: 1024 }
|
||||
);
|
||||
readStream.on('data', (chunk) => {
|
||||
console.log('>>> '+chunk);
|
||||
});
|
||||
readStream.on('end', () => {
|
||||
console.log('### DONE ###');
|
||||
});
|
||||
}
|
||||
|
||||
// 异步遍历器写法
|
||||
async function main(inputFilePath) {
|
||||
const readStream = fs.createReadStream(
|
||||
inputFilePath,
|
||||
{ encoding: 'utf8', highWaterMark: 1024 }
|
||||
);
|
||||
|
||||
for await (const chunk of readStream) {
|
||||
console.log('>>> '+chunk);
|
||||
}
|
||||
console.log('### DONE ###');
|
||||
}
|
||||
```
|
||||
|
||||
## 异步 Generator 函数
|
||||
|
||||
就像 Generator 函数返回一个同步遍历器对象一样,异步 Generator 函数的作用,是返回一个异步遍历器对象。
|
||||
|
||||
在语法上,异步 Generator 函数就是`async`函数与 Generator 函数的结合。
|
||||
|
||||
```javascript
|
||||
async function* gen() {
|
||||
yield 'hello';
|
||||
}
|
||||
const genObj = gen();
|
||||
genObj.next().then(x => console.log(x));
|
||||
// { value: 'hello', done: false }
|
||||
```
|
||||
|
||||
上面代码中,`gen`是一个异步 Generator 函数,执行后返回一个异步 Iterator 对象。对该对象调用`next`方法,返回一个 Promise 对象。
|
||||
|
||||
异步遍历器的设计目的之一,就是 Generator 函数处理同步操作和异步操作时,能够使用同一套接口。
|
||||
|
||||
```javascript
|
||||
// 同步 Generator 函数
|
||||
function* map(iterable, func) {
|
||||
const iter = iterable[Symbol.iterator]();
|
||||
while (true) {
|
||||
const {value, done} = iter.next();
|
||||
if (done) break;
|
||||
yield func(value);
|
||||
}
|
||||
}
|
||||
|
||||
// 异步 Generator 函数
|
||||
async function* map(iterable, func) {
|
||||
const iter = iterable[Symbol.asyncIterator]();
|
||||
while (true) {
|
||||
const {value, done} = await iter.next();
|
||||
if (done) break;
|
||||
yield func(value);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
上面代码中,`map`是一个 Generator 函数,第一个参数是可遍历对象`iterable`,第二个参数是一个回调函数`func`。`map`的作用是将`iterable`每一步返回的值,使用`func`进行处理。上面有两个版本的`map`,前一个处理同步遍历器,后一个处理异步遍历器,可以看到两个版本的写法基本上是一致的。
|
||||
|
||||
下面是另一个异步 Generator 函数的例子。
|
||||
|
||||
```javascript
|
||||
async function* readLines(path) {
|
||||
let file = await fileOpen(path);
|
||||
|
||||
try {
|
||||
while (!file.EOF) {
|
||||
yield await file.readLine();
|
||||
}
|
||||
} finally {
|
||||
await file.close();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
上面代码中,异步操作前面使用`await`关键字标明,即`await`后面的操作,应该返回 Promise 对象。凡是使用`yield`关键字的地方,就是`next`方法停下来的地方,它后面的表达式的值(即`await file.readLine()`的值),会作为`next()`返回对象的`value`属性,这一点是与同步 Generator 函数一致的。
|
||||
|
||||
异步 Generator 函数内部,能够同时使用`await`和`yield`命令。可以这样理解,`await`命令用于将外部操作产生的值输入函数内部,`yield`命令用于将函数内部的值输出。
|
||||
|
||||
上面代码定义的异步 Generator 函数的用法如下。
|
||||
|
||||
```javascript
|
||||
(async function () {
|
||||
for await (const line of readLines(filePath)) {
|
||||
console.log(line);
|
||||
}
|
||||
})()
|
||||
```
|
||||
|
||||
异步 Generator 函数可以与`for await...of`循环结合起来使用。
|
||||
|
||||
```javascript
|
||||
async function* prefixLines(asyncIterable) {
|
||||
for await (const line of asyncIterable) {
|
||||
yield '> ' + line;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
异步 Generator 函数的返回值是一个异步 Iterator,即每次调用它的`next`方法,会返回一个 Promise 对象,也就是说,跟在`yield`命令后面的,应该是一个 Promise 对象。如果像上面那个例子那样,`yield`命令后面是一个字符串,会被自动包装成一个 Promise 对象。
|
||||
|
||||
```javascript
|
||||
function fetchRandom() {
|
||||
const url = 'https://www.random.org/decimal-fractions/'
|
||||
+ '?num=1&dec=10&col=1&format=plain&rnd=new';
|
||||
return fetch(url);
|
||||
}
|
||||
|
||||
async function* asyncGenerator() {
|
||||
console.log('Start');
|
||||
const result = await fetchRandom(); // (A)
|
||||
yield 'Result: ' + await result.text(); // (B)
|
||||
console.log('Done');
|
||||
}
|
||||
|
||||
const ag = asyncGenerator();
|
||||
ag.next().then(({value, done}) => {
|
||||
console.log(value);
|
||||
})
|
||||
```
|
||||
|
||||
上面代码中,`ag`是`asyncGenerator`函数返回的异步遍历器对象。调用`ag.next()`以后,上面代码的执行顺序如下。
|
||||
|
||||
1. `ag.next()`立刻返回一个 Promise 对象。
|
||||
1. `asyncGenerator`函数开始执行,打印出`Start`。
|
||||
1. `await`命令返回一个 Promise 对象,`asyncGenerator`函数停在这里。
|
||||
1. A 处变成 fulfilled 状态,产生的值放入`result`变量,`asyncGenerator`函数继续往下执行。
|
||||
1. 函数在 B 处的`yield`暂停执行,一旦`yield`命令取到值,`ag.next()`返回的那个 Promise 对象变成 fulfilled 状态。
|
||||
1. `ag.next()`后面的`then`方法指定的回调函数开始执行。该回调函数的参数是一个对象`{value, done}`,其中`value`的值是`yield`命令后面的那个表达式的值,`done`的值是`false`。
|
||||
|
||||
A 和 B 两行的作用类似于下面的代码。
|
||||
|
||||
```javascript
|
||||
return new Promise((resolve, reject) => {
|
||||
fetchRandom()
|
||||
.then(result => result.text())
|
||||
.then(result => {
|
||||
resolve({
|
||||
value: 'Result: ' + result,
|
||||
done: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
如果异步 Generator 函数抛出错误,会导致 Promise 对象的状态变为`reject`,然后抛出的错误被`catch`方法捕获。
|
||||
|
||||
```javascript
|
||||
async function* asyncGenerator() {
|
||||
throw new Error('Problem!');
|
||||
}
|
||||
|
||||
asyncGenerator()
|
||||
.next()
|
||||
.catch(err => console.log(err)); // Error: Problem!
|
||||
```
|
||||
|
||||
注意,普通的 async 函数返回的是一个 Promise 对象,而异步 Generator 函数返回的是一个异步 Iterator 对象。可以这样理解,async 函数和异步 Generator 函数,是封装异步操作的两种方法,都用来达到同一种目的。区别在于,前者自带执行器,后者通过`for await...of`执行,或者自己编写执行器。下面就是一个异步 Generator 函数的执行器。
|
||||
|
||||
```javascript
|
||||
async function takeAsync(asyncIterable, count = Infinity) {
|
||||
const result = [];
|
||||
const iterator = asyncIterable[Symbol.asyncIterator]();
|
||||
while (result.length < count) {
|
||||
const {value, done} = await iterator.next();
|
||||
if (done) break;
|
||||
result.push(value);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
```
|
||||
|
||||
上面代码中,异步 Generator 函数产生的异步遍历器,会通过`while`循环自动执行,每当`await iterator.next()`完成,就会进入下一轮循环。一旦`done`属性变为`true`,就会跳出循环,异步遍历器执行结束。
|
||||
|
||||
下面是这个自动执行器的一个使用实例。
|
||||
|
||||
```javascript
|
||||
async function f() {
|
||||
async function* gen() {
|
||||
yield 'a';
|
||||
yield 'b';
|
||||
yield 'c';
|
||||
}
|
||||
|
||||
return await takeAsync(gen());
|
||||
}
|
||||
|
||||
f().then(function (result) {
|
||||
console.log(result); // ['a', 'b', 'c']
|
||||
})
|
||||
```
|
||||
|
||||
异步 Generator 函数出现以后,JavaScript 就有了四种函数形式:普通函数、async 函数、Generator 函数和异步 Generator 函数。请注意区分每种函数的不同之处。基本上,如果是一系列按照顺序执行的异步操作(比如读取文件,然后写入新内容,再存入硬盘),可以使用 async 函数;如果是一系列产生相同数据结构的异步操作(比如一行一行读取文件),可以使用异步 Generator 函数。
|
||||
|
||||
异步 Generator 函数也可以通过`next`方法的参数,接收外部传入的数据。
|
||||
|
||||
```javascript
|
||||
const writer = openFile('someFile.txt');
|
||||
writer.next('hello'); // 立即执行
|
||||
writer.next('world'); // 立即执行
|
||||
await writer.return(); // 等待写入结束
|
||||
```
|
||||
|
||||
上面代码中,`openFile`是一个异步 Generator 函数。`next`方法的参数,向该函数内部的操作传入数据。每次`next`方法都是同步执行的,最后的`await`命令用于等待整个写入操作结束。
|
||||
|
||||
最后,同步的数据结构,也可以使用异步 Generator 函数。
|
||||
|
||||
```javascript
|
||||
async function* createAsyncIterable(syncIterable) {
|
||||
for (const elem of syncIterable) {
|
||||
yield elem;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
上面代码中,由于没有异步操作,所以也就没有使用`await`关键字。
|
||||
|
||||
## yield\* 语句
|
||||
|
||||
`yield*`语句也可以跟一个异步遍历器。
|
||||
|
||||
```javascript
|
||||
async function* gen1() {
|
||||
yield 'a';
|
||||
yield 'b';
|
||||
return 2;
|
||||
}
|
||||
|
||||
async function* gen2() {
|
||||
// result 最终会等于 2
|
||||
const result = yield* gen1();
|
||||
}
|
||||
```
|
||||
|
||||
上面代码中,`gen2`函数里面的`result`变量,最后的值是`2`。
|
||||
|
||||
与同步 Generator 函数一样,`for await...of`循环会展开`yield*`。
|
||||
|
||||
```javascript
|
||||
(async function () {
|
||||
for await (const x of gen2()) {
|
||||
console.log(x);
|
||||
}
|
||||
})();
|
||||
// a
|
||||
// b
|
||||
```
|
||||
|
552
docs/async.md
552
docs/async.md
@ -242,13 +242,34 @@ class Sleep {
|
||||
}
|
||||
|
||||
(async () => {
|
||||
const actualTime = await new Sleep(1000);
|
||||
console.log(actualTime);
|
||||
const sleepTime = await new Sleep(1000);
|
||||
console.log(sleepTime);
|
||||
})();
|
||||
// 1000
|
||||
```
|
||||
|
||||
上面代码中,`await`命令后面是一个`Sleep`对象的实例。这个实例不是 Promise 对象,但是因为定义了`then`方法,`await`会将其视为`Promise`处理。
|
||||
|
||||
这个例子还演示了如何实现休眠效果。JavaScript 一直没有休眠的语法,但是借助`await`命令就可以让程序停顿指定的时间。下面给出了一个简化的`sleep`实现。
|
||||
|
||||
```javascript
|
||||
function sleep(interval) {
|
||||
return new Promise(resolve => {
|
||||
setTimeout(resolve, interval);
|
||||
})
|
||||
}
|
||||
|
||||
// 用法
|
||||
async function one2FiveInAsync() {
|
||||
for(let i = 1; i <= 5; i++) {
|
||||
console.log(i);
|
||||
await sleep(1000);
|
||||
}
|
||||
}
|
||||
|
||||
one2FiveInAsync();
|
||||
```
|
||||
|
||||
`await`命令后面的 Promise 对象如果变为`reject`状态,则`reject`的参数会被`catch`方法的回调函数接收到。
|
||||
|
||||
```javascript
|
||||
@ -486,24 +507,6 @@ async function dbFuc(db) {
|
||||
}
|
||||
```
|
||||
|
||||
目前,[`esm`](https://www.npmjs.com/package/esm)模块加载器支持顶层`await`,即`await`命令可以不放在 async 函数里面,直接使用。
|
||||
|
||||
```javascript
|
||||
// async 函数的写法
|
||||
const start = async () => {
|
||||
const res = await fetch('google.com');
|
||||
return res.text();
|
||||
};
|
||||
|
||||
start().then(console.log);
|
||||
|
||||
// 顶层 await 的写法
|
||||
const res = await fetch('google.com');
|
||||
console.log(await res.text());
|
||||
```
|
||||
|
||||
上面代码中,第二种写法的脚本必须使用`esm`加载器,才会生效。
|
||||
|
||||
第四点,async 函数可以保留运行堆栈。
|
||||
|
||||
```javascript
|
||||
@ -702,410 +705,155 @@ async function logInOrder(urls) {
|
||||
|
||||
上面代码中,虽然`map`方法的参数是`async`函数,但它是并发执行的,因为只有`async`函数内部是继发执行,外部不受影响。后面的`for..of`循环内部使用了`await`,因此实现了按顺序输出。
|
||||
|
||||
## 异步遍历器
|
||||
## 顶层 await
|
||||
|
||||
《遍历器》一章说过,Iterator 接口是一种数据遍历的协议,只要调用遍历器对象的`next`方法,就会得到一个对象,表示当前遍历指针所在的那个位置的信息。`next`方法返回的对象的结构是`{value, done}`,其中`value`表示当前的数据的值,`done`是一个布尔值,表示遍历是否结束。
|
||||
|
||||
这里隐含着一个规定,`next`方法必须是同步的,只要调用就必须立刻返回值。也就是说,一旦执行`next`方法,就必须同步地得到`value`和`done`这两个属性。如果遍历指针正好指向同步操作,当然没有问题,但对于异步操作,就不太合适了。目前的解决方法是,Generator 函数里面的异步操作,返回一个 Thunk 函数或者 Promise 对象,即`value`属性是一个 Thunk 函数或者 Promise 对象,等待以后返回真正的值,而`done`属性则还是同步产生的。
|
||||
|
||||
ES2018 [引入](https://github.com/tc39/proposal-async-iteration)了“异步遍历器”(Async Iterator),为异步操作提供原生的遍历器接口,即`value`和`done`这两个属性都是异步产生。
|
||||
|
||||
### 异步遍历的接口
|
||||
|
||||
异步遍历器的最大的语法特点,就是调用遍历器的`next`方法,返回的是一个 Promise 对象。
|
||||
根据语法规格,`await`命令只能出现在 async 函数内部,否则都会报错。
|
||||
|
||||
```javascript
|
||||
asyncIterator
|
||||
.next()
|
||||
.then(
|
||||
({ value, done }) => /* ... */
|
||||
);
|
||||
// 报错
|
||||
const data = await fetch('https://api.example.com');
|
||||
```
|
||||
|
||||
上面代码中,`asyncIterator`是一个异步遍历器,调用`next`方法以后,返回一个 Promise 对象。因此,可以使用`then`方法指定,这个 Promise 对象的状态变为`resolve`以后的回调函数。回调函数的参数,则是一个具有`value`和`done`两个属性的对象,这个跟同步遍历器是一样的。
|
||||
上面代码中,`await`命令独立使用,没有放在 async 函数里面,就会报错。
|
||||
|
||||
我们知道,一个对象的同步遍历器的接口,部署在`Symbol.iterator`属性上面。同样地,对象的异步遍历器接口,部署在`Symbol.asyncIterator`属性上面。不管是什么样的对象,只要它的`Symbol.asyncIterator`属性有值,就表示应该对它进行异步遍历。
|
||||
|
||||
下面是一个异步遍历器的例子。
|
||||
目前,有一个[语法提案](https://github.com/tc39/proposal-top-level-await),允许在模块的顶层独立使用`await`命令。这个提案的目的,是借用`await`解决模块异步加载的问题。
|
||||
|
||||
```javascript
|
||||
const asyncIterable = createAsyncIterable(['a', 'b']);
|
||||
const asyncIterator = asyncIterable[Symbol.asyncIterator]();
|
||||
// awaiting.js
|
||||
let output;
|
||||
async function main() {
|
||||
const dynamic = await import(someMission);
|
||||
const data = await fetch(url);
|
||||
output = someProcess(dynamic.default, data);
|
||||
}
|
||||
main();
|
||||
export { output };
|
||||
```
|
||||
|
||||
asyncIterator
|
||||
.next()
|
||||
.then(iterResult1 => {
|
||||
console.log(iterResult1); // { value: 'a', done: false }
|
||||
return asyncIterator.next();
|
||||
})
|
||||
.then(iterResult2 => {
|
||||
console.log(iterResult2); // { value: 'b', done: false }
|
||||
return asyncIterator.next();
|
||||
})
|
||||
.then(iterResult3 => {
|
||||
console.log(iterResult3); // { value: undefined, done: true }
|
||||
上面代码中,模块`awaiting.js`的输出值`output`,取决于异步操作。我们把异步操作包装在一个 async 函数里面,然后调用这个函数,只有等里面的异步操作都执行,变量`output`才会有值,否则就返回`undefined`。
|
||||
|
||||
上面的代码也可以写成立即执行函数的形式。
|
||||
|
||||
```javascript
|
||||
// awaiting.js
|
||||
let output;
|
||||
(async function main() {
|
||||
const dynamic = await import(someMission);
|
||||
const data = await fetch(url);
|
||||
output = someProcess(dynamic.default, data);
|
||||
})();
|
||||
export { output };
|
||||
```
|
||||
|
||||
下面是加载这个模块的写法。
|
||||
|
||||
```javascript
|
||||
// usage.js
|
||||
import { output } from "./awaiting.js";
|
||||
|
||||
function outputPlusValue(value) { return output + value }
|
||||
|
||||
console.log(outputPlusValue(100));
|
||||
setTimeout(() => console.log(outputPlusValue(100), 1000);
|
||||
```
|
||||
|
||||
上面代码中,`outputPlusValue()`的执行结果,完全取决于执行的时间。如果`awaiting.js`里面的异步操作没执行完,加载进来的`output`的值就是`undefined`。
|
||||
|
||||
目前的解决方法,就是让原始模块输出一个 Promise 对象,从这个 Promise 对象判断异步操作有没有结束。
|
||||
|
||||
```javascript
|
||||
// awaiting.js
|
||||
let output;
|
||||
export default (async function main() {
|
||||
const dynamic = await import(someMission);
|
||||
const data = await fetch(url);
|
||||
output = someProcess(dynamic.default, data);
|
||||
})();
|
||||
export { output };
|
||||
```
|
||||
|
||||
上面代码中,`awaiting.js`除了输出`output`,还默认输出一个 Promise 对象(async 函数立即执行后,返回一个 Promise 对象),从这个对象判断异步操作是否结束。
|
||||
|
||||
下面是加载这个模块的新的写法。
|
||||
|
||||
```javascript
|
||||
// usage.js
|
||||
import promise, { output } from "./awaiting.js";
|
||||
|
||||
function outputPlusValue(value) { return output + value }
|
||||
|
||||
promise.then(() => {
|
||||
console.log(outputPlusValue(100));
|
||||
setTimeout(() => console.log(outputPlusValue(100), 1000);
|
||||
});
|
||||
```
|
||||
|
||||
上面代码中,异步遍历器其实返回了两次值。第一次调用的时候,返回一个 Promise 对象;等到 Promise 对象`resolve`了,再返回一个表示当前数据成员信息的对象。这就是说,异步遍历器与同步遍历器最终行为是一致的,只是会先返回 Promise 对象,作为中介。
|
||||
上面代码中,将`awaiting.js`对象的输出,放在`promise.then()`里面,这样就能保证异步操作完成以后,才去读取`output`。
|
||||
|
||||
由于异步遍历器的`next`方法,返回的是一个 Promise 对象。因此,可以把它放在`await`命令后面。
|
||||
这种写法比较麻烦,等于要求模块的使用者遵守一个额外的使用协议,按照特殊的方法使用这个模块。一旦你忘了要用 Promise 加载,只使用正常的加载方法,依赖这个模块的代码就可能出错。而且,如果上面的`usage.js`又有对外的输出,等于这个依赖链的所有模块都要使用 Promise 加载。
|
||||
|
||||
顶层的`await`命令,就是为了解决这个问题。它保证只有异步操作完成,模块才会输出值。
|
||||
|
||||
```javascript
|
||||
async function f() {
|
||||
const asyncIterable = createAsyncIterable(['a', 'b']);
|
||||
const asyncIterator = asyncIterable[Symbol.asyncIterator]();
|
||||
console.log(await asyncIterator.next());
|
||||
// { value: 'a', done: false }
|
||||
console.log(await asyncIterator.next());
|
||||
// { value: 'b', done: false }
|
||||
console.log(await asyncIterator.next());
|
||||
// { value: undefined, done: true }
|
||||
// awaiting.js
|
||||
const dynamic = import(someMission);
|
||||
const data = fetch(url);
|
||||
export const output = someProcess((await dynamic).default, await data);
|
||||
```
|
||||
|
||||
上面代码中,两个异步操作在输出的时候,都加上了`await`命令。只有等到异步操作完成,这个模块才会输出值。
|
||||
|
||||
加载这个模块的写法如下。
|
||||
|
||||
```javascript
|
||||
// usage.js
|
||||
import { output } from "./awaiting.js";
|
||||
function outputPlusValue(value) { return output + value }
|
||||
|
||||
console.log(outputPlusValue(100));
|
||||
setTimeout(() => console.log(outputPlusValue(100), 1000);
|
||||
```
|
||||
|
||||
上面代码的写法,与普通的模块加载完全一样。也就是说,模块的使用者完全不用关心,依赖模块的内部有没有异步操作,正常加载即可。
|
||||
|
||||
这时,模块的加载会等待依赖模块(上例是`awaiting.js`)的异步操作完成,才执行后面的代码,有点像暂停在那里。所以,它总是会得到正确的`output`,不会因为加载时机的不同,而得到不一样的值。
|
||||
|
||||
下面是顶层`await`的一些使用场景。
|
||||
|
||||
```javascript
|
||||
// import() 方法加载
|
||||
const strings = await import(`/i18n/${navigator.language}`);
|
||||
|
||||
// 数据库操作
|
||||
const connection = await dbConnector();
|
||||
|
||||
// 依赖回滚
|
||||
let jQuery;
|
||||
try {
|
||||
jQuery = await import('https://cdn-a.com/jQuery');
|
||||
} catch {
|
||||
jQuery = await import('https://cdn-b.com/jQuery');
|
||||
}
|
||||
```
|
||||
|
||||
上面代码中,`next`方法用`await`处理以后,就不必使用`then`方法了。整个流程已经很接近同步处理了。
|
||||
|
||||
注意,异步遍历器的`next`方法是可以连续调用的,不必等到上一步产生的 Promise 对象`resolve`以后再调用。这种情况下,`next`方法会累积起来,自动按照每一步的顺序运行下去。下面是一个例子,把所有的`next`方法放在`Promise.all`方法里面。
|
||||
注意,如果加载多个包含顶层`await`命令的模块,加载命令是同步执行的。
|
||||
|
||||
```javascript
|
||||
const asyncIterable = createAsyncIterable(['a', 'b']);
|
||||
const asyncIterator = asyncIterable[Symbol.asyncIterator]();
|
||||
const [{value: v1}, {value: v2}] = await Promise.all([
|
||||
asyncIterator.next(), asyncIterator.next()
|
||||
]);
|
||||
// x.js
|
||||
console.log("X1");
|
||||
await new Promise(r => setTimeout(r, 1000));
|
||||
console.log("X2");
|
||||
|
||||
console.log(v1, v2); // a b
|
||||
// y.js
|
||||
console.log("Y");
|
||||
|
||||
// z.js
|
||||
import "./x.js";
|
||||
import "./y.js";
|
||||
console.log("Z");
|
||||
```
|
||||
|
||||
另一种用法是一次性调用所有的`next`方法,然后`await`最后一步操作。
|
||||
上面代码有三个模块,最后的`z.js`加载`x.js`和`y.js`,打印结果是`X1`、`Y`、`X2`、`Z`。这说明,`z.js`并没有等待`x.js`加载完成,再去加载`y.js`。
|
||||
|
||||
```javascript
|
||||
async function runner() {
|
||||
const writer = openFile('someFile.txt');
|
||||
writer.next('hello');
|
||||
writer.next('world');
|
||||
await writer.return();
|
||||
}
|
||||
顶层的`await`命令有点像,交出代码的执行权给其他的模块加载,等异步操作完成后,再拿回执行权,继续向下执行。
|
||||
|
||||
runner();
|
||||
```
|
||||
|
||||
### for await...of
|
||||
|
||||
前面介绍过,`for...of`循环用于遍历同步的 Iterator 接口。新引入的`for await...of`循环,则是用于遍历异步的 Iterator 接口。
|
||||
|
||||
```javascript
|
||||
async function f() {
|
||||
for await (const x of createAsyncIterable(['a', 'b'])) {
|
||||
console.log(x);
|
||||
}
|
||||
}
|
||||
// a
|
||||
// b
|
||||
```
|
||||
|
||||
上面代码中,`createAsyncIterable()`返回一个拥有异步遍历器接口的对象,`for...of`循环自动调用这个对象的异步遍历器的`next`方法,会得到一个 Promise 对象。`await`用来处理这个 Promise 对象,一旦`resolve`,就把得到的值(`x`)传入`for...of`的循环体。
|
||||
|
||||
`for await...of`循环的一个用途,是部署了 asyncIterable 操作的异步接口,可以直接放入这个循环。
|
||||
|
||||
```javascript
|
||||
let body = '';
|
||||
|
||||
async function f() {
|
||||
for await(const data of req) body += data;
|
||||
const parsed = JSON.parse(body);
|
||||
console.log('got', parsed);
|
||||
}
|
||||
```
|
||||
|
||||
上面代码中,`req`是一个 asyncIterable 对象,用来异步读取数据。可以看到,使用`for await...of`循环以后,代码会非常简洁。
|
||||
|
||||
如果`next`方法返回的 Promise 对象被`reject`,`for await...of`就会报错,要用`try...catch`捕捉。
|
||||
|
||||
```javascript
|
||||
async function () {
|
||||
try {
|
||||
for await (const x of createRejectingIterable()) {
|
||||
console.log(x);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
注意,`for await...of`循环也可以用于同步遍历器。
|
||||
|
||||
```javascript
|
||||
(async function () {
|
||||
for await (const x of ['a', 'b']) {
|
||||
console.log(x);
|
||||
}
|
||||
})();
|
||||
// a
|
||||
// b
|
||||
```
|
||||
|
||||
Node v10 支持异步遍历器,Stream 就部署了这个接口。下面是读取文件的传统写法与异步遍历器写法的差异。
|
||||
|
||||
```javascript
|
||||
// 传统写法
|
||||
function main(inputFilePath) {
|
||||
const readStream = fs.createReadStream(
|
||||
inputFilePath,
|
||||
{ encoding: 'utf8', highWaterMark: 1024 }
|
||||
);
|
||||
readStream.on('data', (chunk) => {
|
||||
console.log('>>> '+chunk);
|
||||
});
|
||||
readStream.on('end', () => {
|
||||
console.log('### DONE ###');
|
||||
});
|
||||
}
|
||||
|
||||
// 异步遍历器写法
|
||||
async function main(inputFilePath) {
|
||||
const readStream = fs.createReadStream(
|
||||
inputFilePath,
|
||||
{ encoding: 'utf8', highWaterMark: 1024 }
|
||||
);
|
||||
|
||||
for await (const chunk of readStream) {
|
||||
console.log('>>> '+chunk);
|
||||
}
|
||||
console.log('### DONE ###');
|
||||
}
|
||||
```
|
||||
|
||||
### 异步 Generator 函数
|
||||
|
||||
就像 Generator 函数返回一个同步遍历器对象一样,异步 Generator 函数的作用,是返回一个异步遍历器对象。
|
||||
|
||||
在语法上,异步 Generator 函数就是`async`函数与 Generator 函数的结合。
|
||||
|
||||
```javascript
|
||||
async function* gen() {
|
||||
yield 'hello';
|
||||
}
|
||||
const genObj = gen();
|
||||
genObj.next().then(x => console.log(x));
|
||||
// { value: 'hello', done: false }
|
||||
```
|
||||
|
||||
上面代码中,`gen`是一个异步 Generator 函数,执行后返回一个异步 Iterator 对象。对该对象调用`next`方法,返回一个 Promise 对象。
|
||||
|
||||
异步遍历器的设计目的之一,就是 Generator 函数处理同步操作和异步操作时,能够使用同一套接口。
|
||||
|
||||
```javascript
|
||||
// 同步 Generator 函数
|
||||
function* map(iterable, func) {
|
||||
const iter = iterable[Symbol.iterator]();
|
||||
while (true) {
|
||||
const {value, done} = iter.next();
|
||||
if (done) break;
|
||||
yield func(value);
|
||||
}
|
||||
}
|
||||
|
||||
// 异步 Generator 函数
|
||||
async function* map(iterable, func) {
|
||||
const iter = iterable[Symbol.asyncIterator]();
|
||||
while (true) {
|
||||
const {value, done} = await iter.next();
|
||||
if (done) break;
|
||||
yield func(value);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
上面代码中,`map`是一个 Generator 函数,第一个参数是可遍历对象`iterable`,第二个参数是一个回调函数`func`。`map`的作用是将`iterable`每一步返回的值,使用`func`进行处理。上面有两个版本的`map`,前一个处理同步遍历器,后一个处理异步遍历器,可以看到两个版本的写法基本上是一致的。
|
||||
|
||||
下面是另一个异步 Generator 函数的例子。
|
||||
|
||||
```javascript
|
||||
async function* readLines(path) {
|
||||
let file = await fileOpen(path);
|
||||
|
||||
try {
|
||||
while (!file.EOF) {
|
||||
yield await file.readLine();
|
||||
}
|
||||
} finally {
|
||||
await file.close();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
上面代码中,异步操作前面使用`await`关键字标明,即`await`后面的操作,应该返回 Promise 对象。凡是使用`yield`关键字的地方,就是`next`方法停下来的地方,它后面的表达式的值(即`await file.readLine()`的值),会作为`next()`返回对象的`value`属性,这一点是与同步 Generator 函数一致的。
|
||||
|
||||
异步 Generator 函数内部,能够同时使用`await`和`yield`命令。可以这样理解,`await`命令用于将外部操作产生的值输入函数内部,`yield`命令用于将函数内部的值输出。
|
||||
|
||||
上面代码定义的异步 Generator 函数的用法如下。
|
||||
|
||||
```javascript
|
||||
(async function () {
|
||||
for await (const line of readLines(filePath)) {
|
||||
console.log(line);
|
||||
}
|
||||
})()
|
||||
```
|
||||
|
||||
异步 Generator 函数可以与`for await...of`循环结合起来使用。
|
||||
|
||||
```javascript
|
||||
async function* prefixLines(asyncIterable) {
|
||||
for await (const line of asyncIterable) {
|
||||
yield '> ' + line;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
异步 Generator 函数的返回值是一个异步 Iterator,即每次调用它的`next`方法,会返回一个 Promise 对象,也就是说,跟在`yield`命令后面的,应该是一个 Promise 对象。如果像上面那个例子那样,`yield`命令后面是一个字符串,会被自动包装成一个 Promise 对象。
|
||||
|
||||
```javascript
|
||||
function fetchRandom() {
|
||||
const url = 'https://www.random.org/decimal-fractions/'
|
||||
+ '?num=1&dec=10&col=1&format=plain&rnd=new';
|
||||
return fetch(url);
|
||||
}
|
||||
|
||||
async function* asyncGenerator() {
|
||||
console.log('Start');
|
||||
const result = await fetchRandom(); // (A)
|
||||
yield 'Result: ' + await result.text(); // (B)
|
||||
console.log('Done');
|
||||
}
|
||||
|
||||
const ag = asyncGenerator();
|
||||
ag.next().then(({value, done}) => {
|
||||
console.log(value);
|
||||
})
|
||||
```
|
||||
|
||||
上面代码中,`ag`是`asyncGenerator`函数返回的异步遍历器对象。调用`ag.next()`以后,上面代码的执行顺序如下。
|
||||
|
||||
1. `ag.next()`立刻返回一个 Promise 对象。
|
||||
1. `asyncGenerator`函数开始执行,打印出`Start`。
|
||||
1. `await`命令返回一个 Promise 对象,`asyncGenerator`函数停在这里。
|
||||
1. A 处变成 fulfilled 状态,产生的值放入`result`变量,`asyncGenerator`函数继续往下执行。
|
||||
1. 函数在 B 处的`yield`暂停执行,一旦`yield`命令取到值,`ag.next()`返回的那个 Promise 对象变成 fulfilled 状态。
|
||||
1. `ag.next()`后面的`then`方法指定的回调函数开始执行。该回调函数的参数是一个对象`{value, done}`,其中`value`的值是`yield`命令后面的那个表达式的值,`done`的值是`false`。
|
||||
|
||||
A 和 B 两行的作用类似于下面的代码。
|
||||
|
||||
```javascript
|
||||
return new Promise((resolve, reject) => {
|
||||
fetchRandom()
|
||||
.then(result => result.text())
|
||||
.then(result => {
|
||||
resolve({
|
||||
value: 'Result: ' + result,
|
||||
done: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
如果异步 Generator 函数抛出错误,会导致 Promise 对象的状态变为`reject`,然后抛出的错误被`catch`方法捕获。
|
||||
|
||||
```javascript
|
||||
async function* asyncGenerator() {
|
||||
throw new Error('Problem!');
|
||||
}
|
||||
|
||||
asyncGenerator()
|
||||
.next()
|
||||
.catch(err => console.log(err)); // Error: Problem!
|
||||
```
|
||||
|
||||
注意,普通的 async 函数返回的是一个 Promise 对象,而异步 Generator 函数返回的是一个异步 Iterator 对象。可以这样理解,async 函数和异步 Generator 函数,是封装异步操作的两种方法,都用来达到同一种目的。区别在于,前者自带执行器,后者通过`for await...of`执行,或者自己编写执行器。下面就是一个异步 Generator 函数的执行器。
|
||||
|
||||
```javascript
|
||||
async function takeAsync(asyncIterable, count = Infinity) {
|
||||
const result = [];
|
||||
const iterator = asyncIterable[Symbol.asyncIterator]();
|
||||
while (result.length < count) {
|
||||
const {value, done} = await iterator.next();
|
||||
if (done) break;
|
||||
result.push(value);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
```
|
||||
|
||||
上面代码中,异步 Generator 函数产生的异步遍历器,会通过`while`循环自动执行,每当`await iterator.next()`完成,就会进入下一轮循环。一旦`done`属性变为`true`,就会跳出循环,异步遍历器执行结束。
|
||||
|
||||
下面是这个自动执行器的一个使用实例。
|
||||
|
||||
```javascript
|
||||
async function f() {
|
||||
async function* gen() {
|
||||
yield 'a';
|
||||
yield 'b';
|
||||
yield 'c';
|
||||
}
|
||||
|
||||
return await takeAsync(gen());
|
||||
}
|
||||
|
||||
f().then(function (result) {
|
||||
console.log(result); // ['a', 'b', 'c']
|
||||
})
|
||||
```
|
||||
|
||||
异步 Generator 函数出现以后,JavaScript 就有了四种函数形式:普通函数、async 函数、Generator 函数和异步 Generator 函数。请注意区分每种函数的不同之处。基本上,如果是一系列按照顺序执行的异步操作(比如读取文件,然后写入新内容,再存入硬盘),可以使用 async 函数;如果是一系列产生相同数据结构的异步操作(比如一行一行读取文件),可以使用异步 Generator 函数。
|
||||
|
||||
异步 Generator 函数也可以通过`next`方法的参数,接收外部传入的数据。
|
||||
|
||||
```javascript
|
||||
const writer = openFile('someFile.txt');
|
||||
writer.next('hello'); // 立即执行
|
||||
writer.next('world'); // 立即执行
|
||||
await writer.return(); // 等待写入结束
|
||||
```
|
||||
|
||||
上面代码中,`openFile`是一个异步 Generator 函数。`next`方法的参数,向该函数内部的操作传入数据。每次`next`方法都是同步执行的,最后的`await`命令用于等待整个写入操作结束。
|
||||
|
||||
最后,同步的数据结构,也可以使用异步 Generator 函数。
|
||||
|
||||
```javascript
|
||||
async function* createAsyncIterable(syncIterable) {
|
||||
for (const elem of syncIterable) {
|
||||
yield elem;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
上面代码中,由于没有异步操作,所以也就没有使用`await`关键字。
|
||||
|
||||
### yield\* 语句
|
||||
|
||||
`yield*`语句也可以跟一个异步遍历器。
|
||||
|
||||
```javascript
|
||||
async function* gen1() {
|
||||
yield 'a';
|
||||
yield 'b';
|
||||
return 2;
|
||||
}
|
||||
|
||||
async function* gen2() {
|
||||
// result 最终会等于 2
|
||||
const result = yield* gen1();
|
||||
}
|
||||
```
|
||||
|
||||
上面代码中,`gen2`函数里面的`result`变量,最后的值是`2`。
|
||||
|
||||
与同步 Generator 函数一样,`for await...of`循环会展开`yield*`。
|
||||
|
||||
```javascript
|
||||
(async function () {
|
||||
for await (const x of gen2()) {
|
||||
console.log(x);
|
||||
}
|
||||
})();
|
||||
// a
|
||||
// b
|
||||
```
|
||||
|
@ -89,7 +89,7 @@ var firstName = 'Michael';
|
||||
var lastName = 'Jackson';
|
||||
var year = 1958;
|
||||
|
||||
export {firstName, lastName, year};
|
||||
export { firstName, lastName, year };
|
||||
```
|
||||
|
||||
上面代码在`export`命令后面,使用大括号指定所要输出的一组变量。它与前一种写法(直接放置在`var`语句前)是等价的,但是应该优先考虑使用这种写法。因为这样就可以在脚本尾部,一眼看清楚输出了哪些变量。
|
||||
@ -190,7 +190,7 @@ foo()
|
||||
|
||||
```javascript
|
||||
// main.js
|
||||
import {firstName, lastName, year} from './profile.js';
|
||||
import { firstName, lastName, year } from './profile.js';
|
||||
|
||||
function setName(element) {
|
||||
element.textContent = firstName + ' ' + lastName;
|
||||
|
@ -424,7 +424,7 @@ Math.cbrt = Math.cbrt || function(x) {
|
||||
|
||||
### Math.clz32()
|
||||
|
||||
`Math.clz32()`方法将参数转为 32 位无符号整数的形式,然后这个 32 位值里面有多少个前导 0。
|
||||
`Math.clz32()`方法将参数转为 32 位无符号整数的形式,然后返回这个 32 位值里面有多少个前导 0。
|
||||
|
||||
```javascript
|
||||
Math.clz32(0) // 32
|
||||
|
@ -40,7 +40,7 @@ Reflect.has(Object, 'assign') // true
|
||||
```javascript
|
||||
Proxy(target, {
|
||||
set: function(target, name, value, receiver) {
|
||||
var success = Reflect.set(target,name, value, receiver);
|
||||
var success = Reflect.set(target, name, value, receiver);
|
||||
if (success) {
|
||||
console.log('property ' + name + ' on ' + target + ' set to ' + value);
|
||||
}
|
||||
|
@ -43,6 +43,8 @@ String.raw`Hi\u000A!`;
|
||||
```javascript
|
||||
String.raw`Hi\\n`
|
||||
// 返回 "Hi\\\\n"
|
||||
|
||||
String.raw`Hi\\n` === "Hi\\\\n" // true
|
||||
```
|
||||
|
||||
`String.raw()`方法可以作为处理模板字符串的基本方法,它会将所有变量替换,而且对斜杠进行转义,方便下一步作为字符串来使用。
|
||||
@ -123,6 +125,17 @@ for (let ch of s) {
|
||||
// 61
|
||||
```
|
||||
|
||||
另一种方法也可以,使用扩展运算符(`...`)进行展开运算。
|
||||
|
||||
```javascript
|
||||
let arr = [...'𠮷a']; // arr.length === 2
|
||||
arr.forEach(
|
||||
ch => console.log(ch.codePointAt(0).toString(16))
|
||||
);
|
||||
// 20bb7
|
||||
// 61
|
||||
```
|
||||
|
||||
`codePointAt()`方法是测试一个字符由两个字节还是由四个字节组成的最简单方法。
|
||||
|
||||
```javascript
|
||||
|
@ -32,6 +32,7 @@
|
||||
1. [Module 的加载实现](#docs/module-loader)
|
||||
1. [编程风格](#docs/style)
|
||||
1. [读懂规格](#docs/spec)
|
||||
1. [异步遍历器](#docs/async-iterator)
|
||||
1. [ArrayBuffer](#docs/arraybuffer)
|
||||
1. [最新提案](#docs/proposals)
|
||||
1. [Decorator](#docs/decorator)
|
||||
|
Loading…
x
Reference in New Issue
Block a user