关于 Node.js 中的异步迭代器
Node 在 10.0.0 版本中加入了异步迭代器,并且这个功能在最近逐渐赢得了社区的青睐。本文我们将了解什么是异步迭代器,并探索它的使用场景。
什么是异步迭代器
所以什么是异步迭代器?他们其实就是之前迭代器的异步版本。当我们在迭代过程中不清楚值和结束状态时,我们可以使用异步迭代器,用它来解决(resolve)普通的 { value: any, done: boolean }
对象并最终得到 promise。我们也可以使用 for-await-of 循环来帮助我们在异步迭代器中进行循环操作,这就像同步迭代器的 for-of 循环那样。
const asyncIterable = [1, 2, 3];
asyncIterable[Symbol.asyncIterator] = async function*() {
for (let i = 0; i < asyncIterable.length; i++) {
yield { value: asyncIterable[i], done: false }
}
yield { done: true };
};
(async function() {
for await (const part of asyncIterable) {
console.log(part);
}
})();
for-await-of 循环会等待每个它接收的 promise 被解决,然后再执行下一个,这和常规的 for-of 循环是对应的。
目前,除了流以外并没有很多结构支持异步迭代器。但是正如本例所示,可以手动在任何可迭代对象上添加 symbol。
作为异步迭代器的流
在处理流的时候,异步迭代器非常有用。可读流、可写流、双向流、转换流都带有开箱即用的 asyncIterator symbol。
async function printFileToConsole(path) {
try {
const readStream = fs.createReadStream(path, { encoding: 'utf-8' });
for await (const chunk of readStream) {
console.log(chunk);
}
console.log('EOF');
} catch(error) {
console.log(error);
}
}
如果你像这样编写代码,就不必在迭代每个分片时去监听 data
和 end
事件了,并且 for-await-of 循环会在流结束时自行终止。
消费分页的 API
我们也可以借助异步迭代器,从而让我们很容易地从源获取分页过的数据。为此,我们需要某种方式来重构 Node https 请求方法所返回的流的响应体。由于 Node 中请求和响应都是流,我们也可以使用异步迭代器实现这一功能:
const https = require('https');
function homebrewFetch(url) {
return new Promise(async (resolve, reject) => {
const req = https.get(url, async function(res) {
if (res.statusCode >= 400) {
return reject(new Error(`HTTP Status: ${res.statusCode}`));
}
try {
let body = '';
/*
不再使用 res.on 监听流的数据,
而是使用 for-await-of 向剩余响应体
拼接数据切片
*/
for await (const chunk of res) {
body += chunk;
}
// 处理没有响应体的情况
if (!body) resolve({});
// 我们需要解析 body 来获取 json,它是字符串
const result = JSON.parse(body);
resolve(result);
} catch(error) {
reject(error)
}
});
await req;
req.end();
});
}
我们会向 猫猫 API 发起请求,获取一些猫猫图,10 张一页,每个请求中间暂停 7 秒,最多获取 5 页数据,这样就能避免猫猫 API 过载而出现猫病。
function fetchCatPics({ limit, page, done }) {
return homebrewFetch(`https://api.thecatapi.com/v1/images/search?limit=${limit}&page=${page}&order=DESC`)
.then(body => ({ value: body, done }));
}
function catPics({ limit }) {
return {
[Symbol.asyncIterator]: async function*() {
let currentPage = 0;
// 5 页之后停止
while(currentPage < 5) {
try {
const cats = await fetchCatPics({ currentPage, limit, done: false });
console.log(`Fetched ${limit} cats`);
yield cats;
currentPage ++;
} catch(error) {
console.log('There has been an error fetching all the cats!');
console.log(error);
}
}
}
};
}
(async function() {
try {
for await (let catPicPage of catPics({ limit: 10 })) {
console.log(catPicPage);
// 每个请求间等待 7 秒
await new Promise(resolve => setTimeout(resolve, 7000));
}
} catch(error) {
console.log(error);
}
})()
这样一来,我们可以每 7 秒自动获取一整页的猫猫图,然后吸爆。
有一种更常规的翻页手法是实现并暴露 next
和 previous
两个方法,用它们来控制页面导航:
function actualCatPics({ limit }) {
return {
[Symbol.asyncIterator]: () => {
let page = 0;
return {
next: function() {
page++;
return fetchCatPics({ page, limit, done: false });
},
previous: function() {
if (page > 0) {
page--;
return fetchCatPics({ page, limit, done: false });
}
return fetchCatPics({ page: 0, limit, done: true });
}
}
}
};
}
try {
const someCatPics = actualCatPics({ limit: 5 });
const { next, previous } = someCatPics[Symbol.asyncIterator]();
next().then(console.log);
next().then(console.log);
previous().then(console.log);
} catch(error) {
console.log(error);
}
如你所见,当我们有很多页数据要获取,或者类似于在应用的 UI 中要实现无限滚动时,异步迭代器是非常有用的。
浏览器支持这些功能已经有一段时间了,Chrome 从 63 版开始支持,Firefox 从 57 版开始支持,Safari 从 11.1 版开始支持。然而 IE 和 Edge 目前并不支持(译注:Edge 已从 79 版本开始支持了)。
关于异步迭代器的使用场景,你有新点子了吗?你是否已准备好了在实际应用中使用它?
请在下方评论让我们一起交流吧!
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!