cnpm 核心模塊 npminstall 升級到 async 總結

原文來自語雀專欄:www.yuque.com/egg/nodejs/…javascript

做者:蘇千、天豬html

簡單回顧

npminstallcnpm 的核心邏輯庫之一,它經過 link 的方式來安裝 Node.js 依賴,能夠極大的提高安裝速度。java

回顧 npminstall 初版的代碼,默認支持 Node.js 4,那個時候 async/await  還沒成爲 Node.js 的默認功能,各類三方庫仍是 callback 接口。因此咱們選擇基於 co/generator 模式來開發,避免 Callback Hell。node

時光如梭,Node.js 已經發布了 12.x 版本,ES6 早已普及,async/await 早已經在 Node.js 8 就默認開啓,因此咱們決定給 npminstall 進行一次大重構,完全擁抱 async/await,跟 co/generator 說再見。git

再次感謝 TJ,讓咱們提早好多年就享受着 async/await 般的編碼體驗。github

generator 轉 async

這是最容易的替換,幾乎能夠無腦全局替換。npm

  • function* => async function
  • yield => await

老代碼:c#

module.exports = function* (options) {
  // ...
  yield fn();
};
複製代碼

新代碼:promise

module.exports = async options => {
	// ...
  await fn();
};
複製代碼

Promise.all()

值得關注的是併發執行的任務,在 co/generator 模式下只須要 yield tasks 便可實現,而 async/await 模式下須要明確地使用 Promise.all(tasks) 來聲明。併發

老代碼:

const tasks = [];
for (const pkg of pkgs) {
  tasks.push(installOne(pkg));
}
yield tasks;
複製代碼

新代碼:

const tasks = [];
for (const pkg of pkgs) {
  tasks.push(installOne(pkg));
}
await Promise.all(tasks);
複製代碼

經常使用的模塊

co-parallel => p-map

github.com/sindresorhu…

它能夠替代 Promise.all() 且提供併發數限制能力。

最大的思惟差異是 async function 立刻開始執行,而 generator function 是延遲執行。

老代碼:

const parallel = require('co-parallel');

for (const childPkg of pkgs) {
  childPkg.name = childPkg.name || '';
  rootPkgsMap.set(childPkg.name, true);
  options.progresses.installTasks++;
  tasks.push(installOne(options.targetDir, childPkg, options));
}

yield parallel(tasks, 10);
複製代碼

新代碼:

mapper 被調用的時候纔會真實執行。

const pMap = require('p-map');

const mapper = async childPkg => {
  childPkg.name = childPkg.name || '';
  rootPkgsMap.set(childPkg.name, true);
  options.progresses.installTasks++;
  await installOne(options.targetDir, childPkg, options);
};

await pMap(pkgs, mapper, 10);
複製代碼

mz-modules

mz-modules 和 mz 是咱們用的比較多的 2 個模塊。

const { mkdirp, rimraf, sleep } = require('mz-modules');
const { fs } = require('mz');

async function run() {
  // 非阻塞方式刪除目錄
  await rimraf('/path/to/dir');
  
  // +1s
  await sleep('1s');
  
  // 非阻塞的 mkdir -p
  await mkdirp('/path/to/dir');
  
  // 讀取文件,請把 `fs.readFileSync` 從你的頭腦裏面完全遺忘。
  const content = await fs.readFile('/path/to/file.md', 'utf-8');
}

複製代碼

co-fs-extra => fs-extra

fs-extra 已經默認支持 async/await,不須要再使用 co 包裝一層。

老代碼:

const fse = require('co-fs-extra');

yield fse.emptyDir(targetdir);
複製代碼

新代碼:

const fse = require('fs-extra');

await fse.emptyDir(targetdir);
複製代碼

runscript

node-modules/runscript 用於執行一條指令。

const runScript = require('runscript');

async function run() {
  const { stdout, stderr } = await runScript('node -v', { stdio: 'pipe' });
}
複製代碼

yieldable => awaitable

  • 咱們以前在 Egg 1.x 升級 2.x 的時候,也總結了一份更詳細的 yiedable-to-awaitable 指南:
  • 更多 Promise 的語法糖參見:promise-fun 這個倉庫。

總結

重構後總體代碼量其實並不會變化太大,幾乎是等價的代碼量。有一些須要特別回顧的注意點:

  • async function 是會在被調用時當即執行,不像 generator function 是在 yield 的時候才被真正執行。
  • 併發執行須要藉助 Promise.all() 。
  • 須要掌握一些經常使用的輔助庫,如 p-mapmzmz-modules 等。
  • 大膽使用 try catch,它的性能很好。
  • 可能你之後都不須要再使用 co 模塊了。
相關文章
相關標籤/搜索