5

关于 Node.js 中的异步迭代器

 3 years ago
source link: https://segmentfault.com/a/1190000039366803
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

从 10.0.0 版开始,异步迭代器就出现在 Node 中了,在本文中,我们将讨论异步迭代器的作用,以及它们可以用在什么地方。

什么是异步迭代器

异步迭代器实际上是以前迭代器的异步版本。当我们不知道迭代的值和最终状态时,可以使用异步迭代器。两者不同的地方在于,我们得到的 promise 最终将被分解为普通的 { value: any, done: boolean } 对象,另外可以通过 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-of 循环相反,`for-await-of 循环将会等待它收到的每个 promise 解析之后再继续执行下一个。

除了流之外,还在还没有什么能够支持异步迭代的结构,但是可以将 asyncIterator 符号手动添加到任何一种可迭代的结构中。

在流上使用异步迭代器

异步迭代器在处理流时非常有用。可读流、可写流、双工流和转换流上都带有 asyncIterator 符号。

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);
  }
}

如果以这种方式写代码,就不需要在通过迭代获取每个数据块时监听 enddata 事件了,并且 for-await-of 循环会随着流的结束而结束。

用于有分页功能的 API

你还可以通过异步迭代从使用分页的源中轻松获取数据。为了实现这个功能,还需要一种从Node https 请求方法提供给的流中重构响应主体的方法。在这里也可以使用异步迭代器,因为 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({});
        // 需要解析正文来获取 json,因为它是一个字符串
        const result = JSON.parse(body);
        resolve(result);
      } catch(error) {
        reject(error)
      }
    });

    await req;
    req.end();
  });
}

代码通过向 Cat API(https://thecatapi.com/)发出请求,来获取一些猫的图片。另外还添加了 7 秒钟的延迟防止对 cat 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秒钟自动取回一整页的喵星人图片。

一种更常见的页面间导航的方法可实现 nextprevious 方法并将它们公开为控件:

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+ 中可用。

你还能想到可以把异步迭代器用在什么地方吗?欢迎在下面留言!

173382ede7319973.gif


本文首发微信公众号:前端先锋

欢迎扫描二维码关注公众号,每天都给你推送新鲜的前端技术文章

欢迎扫描二维码关注公众号,每天都给你推送新鲜的前端技术文章


欢迎继续阅读本专栏其它高赞文章:



About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK