NodeJs 程序並沒有鎖概念, 可能是單線程程序的緣故吧. 但是存在異步回調, 也就造成並發執行統一代碼的可能性, 當然這里並發不是真正意義上的並發. 是同一線程在不同時間點執行統一代碼. 事故類似代碼如下:
// 阻塞函數
const sleep = async (ms = 0) => {
return new Promise((resolve, reject) => {
return setTimeout(resolve(true), ms);
})
}
let total = 0;
const demoFunc = async () => {
let count = total;
await sleep(2000)
total = count + 1;
}
demoFunc();
demoFunc();
sleep(4000);
console.log(total); // 輸出 1
示例是一個很簡單的自增行為, 很多人可能會說直接在demoFunc()前加 await 不是就是期望結果了, 然后事實可能是真的無法直接這樣做, demoFunc 函數代表其實是一個web請求的處理. total 是數據讀存(db 或 file).
我期望的結果是 2, 實際結果卻為 1. 具體情況不同可能有不同解決方案, 本文提供一種異步隊列機制去順序執行需要互斥的的代碼.
最簡單的互斥執行
只用使用 Promise 鏈執行順序執行互斥代碼.
let queue = Promise.resolve(true);
const queue_exec = async (fn) => {
queue = queue.then(() => {
try {
return Promise.resolve(fn());
} catch(err) {
return Promise.reject(err);
}
});
return queue;
}
// 上述示例換成如下調用即可
queue_exec(demoFunc); // 在實際的請求中可以用 await queue_exec(demoFunc) 或去執行結果.
完善包裝成 NPM 包
上述列表實現過於簡單, 可以做很多優化, 如隊列大小控制, 互斥代碼執行超時, 執行優先級等... 既然實現原理已表明, 錦上添花的功能就不在細化描述, 具體實現參考: AsyncQueue.
假定代碼已經編寫完善, 目錄為 AsyncQueue. 下面記錄發布 NPM 包步驟:
- cd AsyncQueue # 進入代碼目錄
- npm init # npm 初始化 進入交互模式, 可以一路按回車, 最終生成 package.json 文件
- npm login # 登錄 NPM 官網, 交互輸入 npmjs.com 網站的用戶名密碼
- 編輯 package.json 修改相關字段
- name 包名, 不支持大寫, 而且極易與存在的包名沖突, 可以通過 @用戶名/包名 格式做為包名, 避免沖突, 缺點就是包名很長.
- main 入口文件名, 指定 require(包名) 需要加載到文件名
- version 包版本
- 其它字段按需修改即可
- 編寫 README.md 文件, 如包安裝
npm install 包名 --save
包介紹, 用法等, 可選. - npm publish --access=public # 發布包, --access=public 顯示發布公開包
- 變更完成, 更新 package.json 中 version 字段, 重新發布
npm publish --access=public
即可.