JavaScript+TypeScript實現(xiàn)并發(fā)隊列的示例
1. 前言
本文使用了 TypeScript 和 JavaScript,可能有的讀者并沒有學過 TypeScript,擔心看不懂。其實我認為有了 TypeScript 你應該更容易看懂,因為 TypeScript 僅僅是繁瑣了一點,因為它只是給變量加上了類型,但是它能增加代碼的可讀性和可維護性,所以你應該能快速理解。
安裝 TypeScript 見文末。
生活中許多同時發(fā)生的事情,比如:你在打代碼,他在打代碼,她也在打代碼,而我在看你們打代碼。這不是并發(fā)而是并行。
并發(fā)和并行的最大區(qū)別就是多件事情是交給了一個人做還是多個人做。如果是交給了一個人做就是并發(fā),交給了多個人做就是并行。而這里要說的是并發(fā)執(zhí)行,并使用 TypeScript 和 JavaScript 來實現(xiàn)一個并發(fā)隊列。
在生活中我們能處處看到并發(fā)隊列,與本文要說的并發(fā)隊列非常像。比如說排隊,在一個售票窗口,只能一個一個的進行,后面的人只能先等待前面的人買完票了,處理完手續(xù)后才能進行買票。本文要講的并發(fā)隊列原理與這個非常像。
2. 核心代碼解析
先不展示全部的代碼,講清楚核心的邏輯后,其他的代碼也就是起個輔助的作用,也就沒有難理解的地方了。
核心代碼我將其分為以下幾個部分,從易到難進行講解:
- 使用示例
- 添加任務
- 運行任務
- 執(zhí)行一個任務
- 判斷是否執(zhí)行結束
2.1. 執(zhí)行示例
可以看到下面定義并添加了了兩個任務,均在兩秒后輸出一段話到控制臺,但是我們在創(chuàng)建并發(fā)隊列時指定最大并發(fā)數(shù)為 1,所以一次只能執(zhí)行一個任務,并且該任務隊列的執(zhí)行順序是先添加的先執(zhí)行。
// 所有的任務執(zhí)行完畢后的回調函數(shù)
let callback = (result: any) => {
console.log(result);
};
let concurrencyTask = new ConcurrencyTask(1, callback);
// 添加任務
concurrencyTask.addTask((resolve, reject) => {
setTimeout(() => {
console.log("2 秒后得到執(zhí)行"); // 2 秒后輸出
resolve();
}, 2000);
});
concurrencyTask.addTask((resolve, reject) => {
setTimeout(() => {
console.log("4 秒后得到執(zhí)行"); // 4 秒后輸出
resolve();
}, 2000);
});
concurrencyTask.run(false);
2.2. 添加任務
下面是添加任務的代碼,添加的任務要求是一個函數(shù),并在執(zhí)行時會接收到三個參數(shù):resolve,rejecct,args。這三個參數(shù)分別為 Promise 的 resolve 和 reject,而 args 是函數(shù)執(zhí)行需要的可變參數(shù),如果在任務隊列執(zhí)行過程中添加任務則不允許加入。
type Task = (resolve: Function, reject: Function, ...args: Array<any>) => any;
/**
* 添加任務到任務隊列, 不會執(zhí)行
* @param task 任務
* @return 是否添加成功, 如果任務處于執(zhí)行階段返回 false
*/
public addTask(task: Task): boolean {
if (!this.getRunning()) {
this.taskList.push(task);
return true;
}
return false;
}
2.3. 運行任務
canAbort 參數(shù)表示隊列執(zhí)行過程中是否可中斷,在執(zhí)行的任務中調用 reject 函數(shù)即可中斷任務的執(zhí)行,中斷后任務隊列將進行重置,清空已執(zhí)行的和未執(zhí)行的任務以及重置其他數(shù)據。
下面的代碼的意思是執(zhí)行指定最大并發(fā)數(shù)的數(shù)量的任務,如果最大并發(fā)數(shù)大于任務總數(shù)量,則以任務總數(shù)量為最大并發(fā)數(shù)來執(zhí)行。
/**
* 開始運行任務
* @param canAbort 是否可中斷
* @param args 任務執(zhí)行參數(shù)
*/
public run(canAbort: boolean = false, ...args: Array<any>): void {
this.canAbort = canAbort;
this.setRunning(true);
let length = this.taskList.length;
let maxConcurrency = Math.min(this.getMaxConcurrency(), length);
for (let index = 0; index < maxConcurrency; index++) {
this.executeSingleTask(args);
}
}
2.4. 執(zhí)行一個任務
由于任務執(zhí)行具有異步性,所以我們使用 Promise 來包裹任務,并把 resolve, reject 傳遞給任務函數(shù),讓它來決定任務何時結束。
當一個任務調用了 resolve 函數(shù)時,將會判斷任務是否全部得到執(zhí)行,即執(zhí)行 judgeExecuteEnd 函數(shù),如果任務調用 reject 函數(shù),將會判斷是否可以中斷任務的執(zhí)行,并重置任務隊列,當然不想重置任務隊列可以在源代碼上進行修改,這里我就不改了。
然后每個任務的 promise 會保存在 taskPromiseList 變量中,它是一個 Promise 類型的數(shù)組。
/**
* 執(zhí)行單個任務
* @param args 函數(shù)執(zhí)行參數(shù)
*/
private executeSingleTask(...args: Array<any>): void {
let promise = new Promise<void>((resolve, reject) => {
let result = this.taskList[this.taskIndex++](resolve, reject, args);
this.handleResult.push(result);
});
promise.then(() => {
this.judgeExecuteEnd(args);
}).catch((error) => {
// 如果可以中斷任務的執(zhí)行, 則重置任務隊列
if (this.canAbort) {
this.reset();
return;
}
console.error(error);
});
this.taskPromiseList.push(promise);
}
2.5. 判斷是否執(zhí)行結束
下面的代碼中 taskIndex 是當前任務的索引,runOver 為是否執(zhí)行結束的標志。
這里我們判斷 taskPromiseList 中的 promise 是否全部完成
/**
* 判斷是否執(zhí)行結束
* @param args 函數(shù)執(zhí)行所需參數(shù)
*/
private judgeExecuteEnd(args: Array<any>): void {
// 如果全部任務都得到執(zhí)行, 并且執(zhí)行沒有結束
// 設置 runOver 的原因是最后幾個并發(fā)執(zhí)行的任務在執(zhí)行完畢后都會
// 觸發(fā)該函數(shù), 而 runOverCallback 函數(shù)應只執(zhí)行一次
if (this.taskIndex >= this.taskList.length && !this.runOver) {
this.runOver = true;
let result = this.handleResult;
Promise.all(this.taskPromiseList).then(() => {
this.runOverCallback && this.runOverCallback(result);
}).catch((error) => {
// 如果不允許中斷,則會執(zhí)行任務全部完成回調
if(!this.canAbort) {
this.runOverCallback && this.runOverCallback(result);
}
console.error(error);
});
this.reset();
return;
}
// 如果沒有執(zhí)行結束,就執(zhí)行下一個任務
this.executeSingleTask(args);
}
3. 源代碼展示
下面的代碼直接復制到 ts 文件中是不會有任何的效果的,因為瀏覽器不能解析 ts 代碼,我們需要使用 ts 編譯器將其編譯為 js 代碼后,再引用 js 文件即可。安裝 TypeScript 見文末。
/*
功能描述: 并發(fā)隊列
創(chuàng)建時間: 2023年 12月 17日
*/
type Task = (resolve: Function, reject: Function, ...args: Array<any>) => any;
type ResultCallback = (result: Array<any>) => any;
/**
* 并發(fā)任務隊列
*/
class ConcurrencyTask {
/**
* 任務集合
*/
private taskList: Array<Task>;
/**
* 處理結果
*/
private handleResult: Array<any>;
/**
* 是否正在執(zhí)行任務
*/
private running: boolean;
/**
* 最大并發(fā)數(shù)
*/
private maxConcurrency: number;
/**
* 默認的最大并發(fā)數(shù)
*/
private static DEFAULT_MAX_CONCURRENCY: number = 2;
/**
* 當前任務索引
*/
private taskIndex: number;
/**
* 用 promise 包裹任務
*/
private taskPromiseList: Array<Promise<void>>;
/**
* 是否可中斷
*/
private canAbort: boolean;
/**
* 執(zhí)行結束
*/
private runOver: boolean;
/**
* 任務全部執(zhí)行完畢時的回調函數(shù)
*/
private runOverCallback: ResultCallback;
/**
* 創(chuàng)建并發(fā)任務隊列
* @param maxConcurrency 最大并發(fā)數(shù)
* @param runOverCallback 任務全部執(zhí)行完畢后的回調
*/
public constructor(maxConcurrency: number = ConcurrencyTask.DEFAULT_MAX_CONCURRENCY, runOverCallback: ResultCallback) {
this.setRunOverCallback(runOverCallback);
this.setMaxConcurrency(maxConcurrency);
this.initial();
}
private initial(): void {
this.canAbort = false;
this.reset();
}
/**
* 添加任務到任務隊列, 不會執(zhí)行
* @param task 任務
* @return 是否添加成功, 如果任務處于執(zhí)行階段返回 false
*/
public addTask(task: Task): boolean {
if (!this.getRunning()) {
this.taskList.push(task);
return true;
}
return false;
}
/**
* 開始運行任務
* @param canAbort 是否可中斷
* @param args 任務執(zhí)行參數(shù)
*/
public run(canAbort: boolean = false, ...args: Array<any>): void {
this.canAbort = canAbort;
this.setRunning(true);
let length = this.taskList.length;
let maxConcurrency = Math.min(this.getMaxConcurrency(), length);
for (let index = 0; index < maxConcurrency; index++) {
this.executeSingleTask(args);
}
}
/**
* 執(zhí)行單個任務
* @param args 函數(shù)執(zhí)行參數(shù)
*/
private executeSingleTask(...args: Array<any>): void {
let promise = new Promise<void>((resolve, reject) => {
let result = this.taskList[this.taskIndex++](resolve, reject, args);
this.handleResult.push(result);
});
promise.then(() => {
this.judgeExecuteEnd(args);
}).catch((error) => {
// 如果可以中斷任務的執(zhí)行, 則重置任務隊列
if (this.canAbort) {
this.reset();
return;
}
console.error(error);
});
this.taskPromiseList.push(promise);
}
/**
* 判斷是否執(zhí)行結束
* @param args 函數(shù)執(zhí)行所需參數(shù)
*/
private judgeExecuteEnd(args: Array<any>): void {
// 如果全部任務都得到執(zhí)行, 并且執(zhí)行沒有結束
// 設置 runOver 的原因是最后幾個并發(fā)執(zhí)行的任務在執(zhí)行完畢后都會
// 觸發(fā)該函數(shù), 而 runOverCallback 函數(shù)應只執(zhí)行一次
if (this.taskIndex >= this.taskList.length && !this.runOver) {
this.runOver = true;
let result = this.handleResult;
Promise.all(this.taskPromiseList).then(() => {
this.runOverCallback && this.runOverCallback(result);
}).catch((error) => {
if(!this.canAbort) {
this.runOverCallback && this.runOverCallback(result);
}
console.error(error);
});
this.reset();
return;
}
this.executeSingleTask(args);
}
private reset(): void {
this.taskList = [];
this.taskIndex = 0;
this.taskPromiseList = [];
this.running = false;
this.handleResult = [];
}
private setRunning(running: boolean): void {
this.running = running;
}
public getRunning(): boolean {
return this.running;
}
/**
* 設置任務全部執(zhí)行完畢后的回調函數(shù), 如果隊列正在執(zhí)行則返回 false
* @param runOverCallback 回調函數(shù)
*/
public setRunOverCallback(runOverCallback: ResultCallback): boolean {
if(!this.getRunning()) {
this.runOverCallback = runOverCallback;
return true;
}
return false;
}
/**
* 設置最大并發(fā)數(shù), 如果正在執(zhí)行返回 false
* @param maxConcurrency 最大并發(fā)數(shù), 小于等于 0 時使用默認值
*/
public setMaxConcurrency(maxConcurrency: number): boolean {
if(maxConcurrency <= 0) {
this.maxConcurrency = ConcurrencyTask.DEFAULT_MAX_CONCURRENCY;
}
if (!this.getRunning()) {
this.maxConcurrency = maxConcurrency;
return true;
}
return false;
}
public getMaxConcurrency(): number {
return this.maxConcurrency;
}
}
4. 安裝 TypeScript
由于 TypeScript 是運行在 Node.js 上的,所以我們還需要安裝 Node.js,安裝 Node.js 可前往 Node.Js 中文網。
這里僅提供 windows 上的 TypeScript 的安裝方式。
首先以管理員的方式進入 cmd(win + R,輸入 cmd,然后 ctrl + shift + enter 即可)。
使用以下的命令全局安裝:
npm i -g typescript
之后在任意目錄下創(chuàng)建一個 ts 文件,然后在該文件夾下打開 cmd,執(zhí)行 tsc xx.ts 就會得到一個編譯后的 ja 文件。
到此這篇關于JavaScript+TypeScript實現(xiàn)并發(fā)隊列的示例的文章就介紹到這了,更多相關JavaScript TypeScript并發(fā)隊列內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
- Vue3+TypeScript+printjs實現(xiàn)標簽批量打印功能的完整過程
- TypeScript與JavaScript多方面闡述對比相同點和區(qū)別
- JavaScript報錯:Uncaught TypeError: Cannot set property ‘X‘ of undefine的解決方案
- JavaScript報錯:Uncaught TypeError: XXX is not iterable的解決方法
- JavaScript中手動實現(xiàn)Array.prototype.map方法
- 全面解讀TypeScript和JavaScript的區(qū)別
- Vue3項目中配置TypeScript和JavaScript的兼容
- js中<script> 標簽中type值及其含義
相關文章
IE6下出現(xiàn)JavaScript未結束的字符串常量錯誤的解決方法
JavaScript文件只在IE6下出錯(“未結束的字符串常量”)的解決辦法。2010-11-11

