for await ... of
• • ☕️ 1 min read你可以从本文了解到
背景
在看vercel/cli代码的时候什么看到了比较少见的用法。
是for await of
与generator
的结合。稍后会 demo 来说明。
首先谁在用这个写法
在代码搜索平台sourcegraph上搜索关键字for await (const
,可以看到有 n 多大型项目在用。
我们来看 stars 排名第一的 puppeteer 的代码,可以看出这部分是以流的方式分片读取chunk存入buffer。
async function getReadableAsBuffer(
readable: Readable,
path?: string
): Promise<Buffer> {
if (!isNode && path) {
throw new Error(
'Cannot write to a path outside of Node.js environment.'
);
}
const fs = isNode ? await importFSModule() : null;
let fileHandle: import('fs').promises.FileHandle;
if (path && fs) {
fileHandle = await fs.promises.open(path, 'w');
}
const buffers = [];
for await (const chunk of readable) { buffers.push(chunk);
if (fileHandle) {
await fs.promises.writeFile(fileHandle, chunk);
}
}
if (path) await fileHandle.close();
let resultBuffer = null;
try {
resultBuffer = Buffer.concat(buffers);
} finally {
return resultBuffer;
}
}
其他的例子也都差不多,基本模式也就是语法所定义的。of后面接iterable对象(通常是数组、迭代器等等),puppeteer里面用的是数组。vercel用的是iterator。
for await (variable of iterable) {
statement
}
那么来看看vercel的iterator用法。值得注意的是这是一个cli应用,所有的操作都需要通过屏幕输出给用户进行到哪一步了。
为了清晰期间去掉了业务代码,抽象成以下最简demo。
其main函数是在执行一段代码createSomething的时候中途去执行另一段代码createOtherStuff,再回来继续执行createSomething。
可以看到这个模式有一个特点,yield出来的消息最终都抛到了main函数,有一种事件集线器的效果。
async function main() {
for await (const event of createSomething()) {
console.log(event);
// update spinner/indicator
}
}
async function* createSomething() {
yield 'createSomething-1';
yield 'createSomething-2';
for await (const event of createOtherStuff()) {
yield event;
}
yield 'event-done';
}
async function* createOtherStuff() {
yield 'createOtherStuff-1';
yield 'createOtherStuff-2';
yield 'createOtherStuff-done';
}
main();
小节
这是一个很有趣但是很强大的写法,希望之后有机会能再项目中用上。