則詳解:全面禁用 Node.js 同步方法調(diào)用)
ESLint no-sync 規(guī)則詳解全面禁用 Node.js 同步方法調(diào)用【免費(fèi)下載鏈接】eslintFind and fix problems in your JavaScript code.項(xiàng)目地址: https://gitcode.com/GitHub_Trending/es/eslint本篇技術(shù)指南圍繞 ESLint 核心規(guī)則no-sync展開講解該規(guī)則為何存在、如何檢測(cè)以Sync結(jié)尾的同步方法調(diào)用、allowAtRootLevel選項(xiàng)的作用與適用場(chǎng)景并結(jié)合本倉(cāng)庫(kù)源碼與測(cè)試用例剖析其底層實(shí)現(xiàn)原理。讀完本文你將能夠在自己的 Node.js 項(xiàng)目中正確配置并使用該規(guī)則理解它在腳本工具與高并發(fā)服務(wù)兩種場(chǎng)景下的取舍以及規(guī)則在 ESLint v7.0.0 后被棄用時(shí)的遷移方案。規(guī)則背景Node.js 中同步 I/O 的取舍在 Node.js 中絕大多數(shù) I/O 操作都通過異步方法完成例如fs.readFile()、fs.writeFile()。但 Node.js 同時(shí)也為這些異步方法提供了對(duì)應(yīng)的同步版本例如fs.exists()與fs.existsSync()、fs.readFile()與fs.readFileSync()。同步版本的特點(diǎn)是調(diào)用會(huì)阻塞當(dāng)前線程直到操作完成才返回。這在某些場(chǎng)景下是合理的比如命令行工具——ESLint 自身的很多 CLI 邏輯就使用同步操作因?yàn)槟_本進(jìn)程一次性執(zhí)行完就退出阻塞無(wú)傷大雅。然而在另一些場(chǎng)景下同步操作被視為應(yīng)避免的壞實(shí)踐高并發(fā) Web 服務(wù)器如果每個(gè)請(qǐng)求處理中都出現(xiàn)同步 I/O如fs.readFileSync()服務(wù)器進(jìn)程會(huì)在等待期間被鎖死無(wú)法響應(yīng)其他請(qǐng)求導(dǎo)致吞吐量急劇下降交互式應(yīng)用或長(zhǎng)期運(yùn)行的服務(wù)進(jìn)程同步調(diào)用會(huì)把事件循環(huán)卡住影響定時(shí)器、網(wǎng)絡(luò)監(jiān)聽等一切依賴事件循環(huán)的功能。因此是否允許同步操作取決于上下文而no-sync規(guī)則正是為了幫助團(tuán)隊(duì)在這一取舍上建立統(tǒng)一的代碼約束。規(guī)則詳情如何識(shí)別同步方法no-sync規(guī)則的目標(biāo)是阻止在 Node.js 中調(diào)用同步方法。它依據(jù) Node.js 操作的命名慣例專門匹配方法后綴為Sync的調(diào)用。例如fs.existsSync(path)—— 命中后綴為Syncfs.readFileSync(path)—— 命中obj.sync()——不命中因?yàn)閷傩悦莝ync而非以Sync結(jié)尾fs.readFile(path, callback)—— 不命中屬于異步方法。規(guī)則的完整定義位于倉(cāng)庫(kù) lib/rules/no-sync.jscreate(context) { const selector context.options[0] context.options[0].allowAtRootLevel ? :function MemberExpression[property.name/.*Sync$/] : MemberExpression[property.name/.*Sync$/]; return { selector { context.report({ node, messageId: noSync, data: { propertyName: node.property.name, }, }); }, }; },從源碼結(jié)構(gòu)可以清晰看到實(shí)現(xiàn)思路規(guī)則通過esquery 選擇器匹配 AST 節(jié)點(diǎn)MemberExpression[property.name/.*Sync$/]會(huì)命中所有屬性名以Sync結(jié)尾的成員表達(dá)式——無(wú)論是fs.existsSync(...)這樣的方法調(diào)用還是fs.fooSync這樣的屬性引用命中后通過messageId: noSync上報(bào)對(duì)應(yīng)的錯(cuò)誤消息模板為Unexpected sync method: {{propertyName}}.即會(huì)明確指出是哪一個(gè)同步方法觸發(fā)了告警該規(guī)則不提供自動(dòng)修復(fù)autofix因?yàn)閷⑼秸{(diào)用改寫為異步調(diào)用需要人工重寫回調(diào)/async-await 邏輯無(wú)法機(jī)械替換。在配置層面規(guī)則的meta定義了如下骨架同樣見 lib/rules/no-sync.jsmeta: { type: suggestion, docs: { description: Disallow synchronous methods, recommended: false, }, schema: [ { type: object, properties: { allowAtRootLevel: { type: boolean, default: false, }, }, additionalProperties: false, }, ], messages: { noSync: Unexpected sync method: {{propertyName}}., }, },其中type: suggestion表明這是一條建議型規(guī)則不屬于eslint:recommended默認(rèn)開啟集合recommended: false需要開發(fā)者顯式開啟schema則嚴(yán)格校驗(yàn)選項(xiàng)結(jié)構(gòu)只接受{ allowAtRootLevel: boolean }且additionalProperties: false表示不接受任何其他未知屬性。選項(xiàng)詳解allowAtRootLevel該規(guī)則只接受一個(gè)可選對(duì)象選項(xiàng)選項(xiàng)類型默認(rèn)值含義allowAtRootLevelbooleanfalse是否允許在**文件頂層任何函數(shù)之外**使用同步方法默認(rèn)值為false即任何位置的同步方法調(diào)用包括頂層都會(huì)被報(bào)告。當(dāng)設(shè)為true時(shí)規(guī)則只在函數(shù)內(nèi)部報(bào)告同步調(diào)用頂層代碼被視為豁免。這一設(shè)計(jì)的合理性在于文件頂層代碼如初始化腳本、模塊加載階段的配置讀取只在進(jìn)程啟動(dòng)時(shí)執(zhí)行一次即使阻塞也無(wú)礙運(yùn)行期性能而函數(shù)內(nèi)部的同步調(diào)用可能被高頻觸發(fā)風(fēng)險(xiǎn)更高。從源碼看選項(xiàng)切換的實(shí)質(zhì)是改變選擇器allowAtRootLevel: false默認(rèn)→ 選擇器為MemberExpression[property.name/.*Sync$/]全局匹配所有同步成員表達(dá)式allowAtRootLevel: true→ 選擇器變?yōu)?function MemberExpression[property.name/.*Sync$/]前置的:function限定節(jié)點(diǎn)必須位于某個(gè)函數(shù)內(nèi)部頂層調(diào)用因此被豁免。這里的:function是 esquery 的偽類選擇器會(huì)同時(shí)匹配函數(shù)聲明、函數(shù)表達(dá)式、箭頭函數(shù)等方法。倉(cāng)庫(kù)測(cè)試 tests/lib/rules/no-sync.js 中有對(duì)應(yīng)驗(yàn)證var foo fs.fooSync;頂層在allowAtRootLevel: true時(shí)是合法的而function someFunction() {fs.fooSync();}函數(shù)內(nèi)部即便開啟該選項(xiàng)也依然報(bào)錯(cuò)。默認(rèn)選項(xiàng)下的示例錯(cuò)誤示例/*eslint no-sync: error*/即默認(rèn){ allowAtRootLevel: false }/*eslint no-sync: error*/ fs.existsSync(somePath); function foo() { var contents fs.readFileSync(somePath).toString(); }第一行是頂層同步調(diào)用第二處是函數(shù)內(nèi)的同步調(diào)用兩者都會(huì)被報(bào)告。正確示例/*eslint no-sync: error*/ obj.sync(); async(function() { // ... });obj.sync()的屬性名是sync而非Sync不滿足后綴匹配async(function() { ... })是異步風(fēng)格的調(diào)用均不會(huì)觸發(fā)告警。開啟 allowAtRootLevel 后的示例錯(cuò)誤示例{ allowAtRootLevel: true }函數(shù)內(nèi)的同步調(diào)用仍被攔截/*eslint no-sync: [error, { allowAtRootLevel: true }]*/ function foo() { var contents fs.readFileSync(somePath).toString(); } var bar baz fs.readFileSync(qux);箭頭函數(shù)體中的fs.readFileSync(qux)同樣屬于函數(shù)內(nèi)部依舊會(huì)報(bào)錯(cuò)。正確示例/*eslint no-sync: [error, { allowAtRootLevel: true }]*/ fs.readFileSync(somePath).toString();文件頂層的同步調(diào)用在啟動(dòng)階段執(zhí)行屬于被允許的例外。源碼與測(cè)試印證測(cè)試用例覆蓋的行為邊界測(cè)試文件 tests/lib/rules/no-sync.js 通過RuleTester定義了規(guī)則的合法與非法行為值得關(guān)注的邊界包括屬性引用也會(huì)被命中var foo fs.fooSync;不調(diào)用僅引用在默認(rèn)配置下同樣報(bào)錯(cuò)說明規(guī)則針對(duì)的是“任何以Sync結(jié)尾的成員表達(dá)式”而非僅限函數(shù)調(diào)用表達(dá)式深層成員表達(dá)式不會(huì)誤報(bào)var foo fs.foo.foo();是合法代碼——中間的屬性鏈最終調(diào)用的是foo()不滿足后綴條件函數(shù)位置決定是否豁免if (true) {fs.fooSync();}在allowAtRootLevel: true下合法頂層塊內(nèi)仍視為根級(jí)而function someFunction() {fs.fooSync();}和var a function someFunction() {fs.fooSync();}即使開啟該選項(xiàng)也依然非法在函數(shù)體內(nèi)錯(cuò)誤消息數(shù)據(jù)所有非法用例均斷言messageId: noSync且data.propertyName指向具體的同步方法名如fooSync與源碼中的報(bào)告邏輯一一對(duì)應(yīng)。規(guī)則注冊(cè)與元數(shù)據(jù)no-sync通過 lib/rules/index.js 中的懶加載注冊(cè)表對(duì)外暴露no-sync: () require(./no-sync)并同步收錄在文檔站點(diǎn)數(shù)據(jù) docs/src/_data/rules_meta.json 中供規(guī)則文檔頁(yè)渲染使用。棄用狀態(tài)與遷移指引需要特別說明no-sync屬于 Node.js/CommonJS 系列規(guī)則該系列共 10 條核心規(guī)則已在ESLint v7.0.0中被棄用規(guī)則源碼meta.deprecated字段明確記錄了這一狀態(tài)deprecatedSince: 7.0.0、availableUntil: 11.0.0遷移說明詳見倉(cāng)庫(kù)文檔 migrating-to-7.0.0.md 的 “Node.js/CommonJS rules have been deprecated” 一節(jié)。棄用并不意味著立即移除。按照 ESLint 的棄用政策見 rule-deprecation.md棄用后的規(guī)則仍會(huì)保留在核心中供繼續(xù)使用但團(tuán)隊(duì)不再修復(fù) bug、不再增加功能、不再更新文檔并可能在未來的大版本中移除。與此同時(shí)功能等價(jià)、持續(xù)維護(hù)的版本由eslint-plugin-n插件及其前身eslint-plugin-node提供其中no-sync的對(duì)應(yīng)規(guī)則為node/no-sync。若項(xiàng)目依賴該檢查推薦遷移到插件版本以獲得持續(xù)支持。遷移到插件的典型步驟以 flat config 為例在配置文件eslint.config.js中import n from eslint-plugin-n; export default [ { plugins: { n }, rules: { n/no-sync: [error, { allowAtRootLevel: false }], }, }, ];對(duì)于仍在使用舊版.eslintrc的項(xiàng)目則在plugins中聲明n并將規(guī)則名寫為n/no-sync。何時(shí)不要使用該規(guī)則如果項(xiàng)目本身就是一個(gè)以同步操作為主的腳本如構(gòu)建腳本、CLI 工具、一次性數(shù)據(jù)遷移任務(wù)同步調(diào)用不會(huì)帶來阻塞風(fēng)險(xiǎn)此時(shí)不必開啟no-sync以免產(chǎn)生大量無(wú)意義的告警。規(guī)則文檔的 “When Not To Use It” 一節(jié)給出的建議正是如果你希望允許腳本中的同步操作就不要啟用這條規(guī)則。此外若團(tuán)隊(duì)采用eslint-plugin-n插件并統(tǒng)一管理 Node 相關(guān)規(guī)則也應(yīng)直接在插件層面配置n/no-sync避免與核心中已棄用的規(guī)則混用。延伸閱讀規(guī)則源碼與選項(xiàng) schemalib/rules/no-sync.js規(guī)則測(cè)試用例tests/lib/rules/no-sync.js規(guī)則注冊(cè)入口lib/rules/index.jsNode.js 系列規(guī)則棄用遷移說明docs/src/use/migrating-to-7.0.0.mdESLint 規(guī)則棄用政策docs/src/use/rule-deprecation.md規(guī)則元數(shù)據(jù)文檔站點(diǎn)數(shù)據(jù)源docs/src/_data/rules_meta.json【免費(fèi)下載鏈接】eslintFind and fix problems in your JavaScript code.項(xiàng)目地址: https://gitcode.com/GitHub_Trending/es/eslint創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考