MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

JavaScript基于类对象和闭包模块的并发处理

2024-10-172.6k 阅读

JavaScript 并发处理基础

在 JavaScript 中,由于其单线程的特性,并发处理并不是传统意义上像多线程语言那样同时执行多个任务。JavaScript 的并发是通过事件循环(Event Loop)机制来实现的,它允许在执行一个任务的同时,不会阻塞其他任务的执行。

事件循环会不断检查调用栈(Call Stack)是否为空,当调用栈为空时,它会从任务队列(Task Queue)中取出一个任务放入调用栈执行。这种机制使得 JavaScript 可以在执行长时间运行的任务(如网络请求、文件读取等)时,依然能够响应用户的交互操作。

例如,下面是一个简单的示例,展示了 JavaScript 如何在不阻塞主线程的情况下执行异步任务:

console.log('Start');

setTimeout(() => {
    console.log('Timeout');
}, 1000);

console.log('End');

在上述代码中,console.log('Start') 首先执行,然后 setTimeout 被调用,它会在 1000 毫秒后将回调函数放入任务队列。接着 console.log('End') 执行。当调用栈为空时,事件循环会从任务队列中取出 setTimeout 的回调函数并放入调用栈执行,最终输出 Timeout

基于类对象的并发处理

类对象的基本概念

在 JavaScript 中,类是一种用于创建对象的模板。ES6 引入了 class 关键字,使得创建类和对象更加直观和面向对象化。一个类可以包含属性和方法,通过 new 关键字可以创建类的实例。

例如:

class MyClass {
    constructor() {
        this.property = 'Initial value';
    }

    myMethod() {
        console.log('This is my method.');
    }
}

const instance = new MyClass();
console.log(instance.property);
instance.myMethod();

利用类对象管理并发任务

我们可以创建一个类来管理并发任务。比如,创建一个 TaskManager 类,它可以维护一个任务队列,并通过事件循环机制依次执行这些任务。

class TaskManager {
    constructor() {
        this.taskQueue = [];
    }

    addTask(task) {
        this.taskQueue.push(task);
        this.executeNextTask();
    }

    executeNextTask() {
        if (this.taskQueue.length > 0) {
            const task = this.taskQueue.shift();
            task();
            this.executeNextTask();
        }
    }
}

const manager = new TaskManager();

const task1 = () => {
    console.log('Task 1 is running');
};

const task2 = () => {
    console.log('Task 2 is running');
};

manager.addTask(task1);
manager.addTask(task2);

在上述代码中,TaskManager 类有一个 taskQueue 用于存储任务,addTask 方法用于将任务添加到队列并触发执行下一个任务,executeNextTask 方法会从队列中取出一个任务并执行,然后递归调用自身执行下一个任务。

并发任务的同步与异步

在实际应用中,任务可能是同步的,也可能是异步的。对于异步任务,我们通常使用 Promiseasync/await 来处理。

例如,假设我们有一个异步任务,模拟网络请求:

class TaskManager {
    constructor() {
        this.taskQueue = [];
    }

    addTask(task) {
        this.taskQueue.push(task);
        this.executeNextTask();
    }

    async executeNextTask() {
        if (this.taskQueue.length > 0) {
            const task = this.taskQueue.shift();
            await task();
            this.executeNextTask();
        }
    }
}

const manager = new TaskManager();

const asyncTask = async () => {
    return new Promise((resolve) => {
        setTimeout(() => {
            console.log('Async task is running');
            resolve();
        }, 1000);
    });
};

manager.addTask(asyncTask);

在这个例子中,asyncTask 是一个异步任务,通过 PromisesetTimeout 模拟了网络请求的延迟。executeNextTask 方法被标记为 async,并且使用 await 来等待异步任务完成,确保任务顺序执行。

闭包模块与并发处理

闭包的概念

闭包是 JavaScript 中一个强大的特性,它允许函数访问其外部作用域的变量,即使外部作用域已经执行完毕。简单来说,闭包就是函数和其周围状态(词法环境)的组合。

例如:

function outerFunction() {
    const outerVariable = 'I am from outer function';

    function innerFunction() {
        console.log(outerVariable);
    }

    return innerFunction;
}

const closure = outerFunction();
closure();

在上述代码中,innerFunction 形成了一个闭包,它可以访问 outerFunction 作用域中的 outerVariable。即使 outerFunction 已经执行完毕,outerVariable 依然可以被 innerFunction 访问,因为 innerFunction 持有对 outerFunction 作用域的引用。

闭包模块模式

闭包模块模式是利用闭包来创建模块化的代码结构。通过这种模式,我们可以将代码封装在一个闭包内部,只暴露必要的接口,从而实现代码的模块化和隐私保护。

例如:

const myModule = (function () {
    const privateVariable = 'This is a private variable';

    function privateFunction() {
        console.log('This is a private function');
    }

    return {
        publicMethod: function () {
            console.log('Accessing private variable:', privateVariable);
            privateFunction();
        }
    };
})();

myModule.publicMethod();

在上述代码中,闭包内部的 privateVariableprivateFunction 是私有的,外部无法直接访问。通过返回一个包含 publicMethod 的对象,我们提供了一个公共接口来间接访问闭包内部的私有成员。

闭包模块在并发处理中的应用

在并发处理中,闭包模块可以用于封装并发任务的逻辑,使得代码更加模块化和可维护。例如,我们可以创建一个闭包模块来管理多个异步任务的并发执行,并处理任务的结果。

const taskModule = (function () {
    const tasks = [];

    function addTask(task) {
        tasks.push(task);
    }

    async function executeTasks() {
        const results = await Promise.all(tasks.map(task => task()));
        console.log('Task results:', results);
    }

    return {
        addTask,
        executeTasks
    };
})();

const task1 = async () => {
    return new Promise((resolve) => {
        setTimeout(() => {
            console.log('Task 1 completed');
            resolve('Result of task 1');
        }, 1000);
    });
};

const task2 = async () => {
    return new Promise((resolve) => {
        setTimeout(() => {
            console.log('Task 2 completed');
            resolve('Result of task 2');
        }, 1500);
    });
};

taskModule.addTask(task1);
taskModule.addTask(task2);
taskModule.executeTasks();

在上述代码中,taskModule 是一个闭包模块,它内部维护了一个任务数组 tasksaddTask 方法用于添加任务,executeTasks 方法使用 Promise.all 来并发执行所有任务,并在所有任务完成后输出结果。这种方式使得并发任务的管理更加模块化,易于扩展和维护。

结合类对象和闭包模块的并发处理

设计思路

将类对象和闭包模块的优势结合起来,可以更好地管理并发任务。我们可以使用类对象来封装任务的状态和行为,而闭包模块则用于提供更灵活的接口和模块化的代码结构。

例如,我们可以创建一个 ConcurrentTaskManager 类,同时利用闭包模块来管理任务的注册和执行逻辑。

代码实现

const taskRegistrationModule = (function () {
    const registeredTasks = [];

    function registerTask(task) {
        registeredTasks.push(task);
    }

    function getRegisteredTasks() {
        return registeredTasks;
    }

    return {
        registerTask,
        getRegisteredTasks
    };
})();

class ConcurrentTaskManager {
    constructor() {
        this.taskResults = [];
    }

    async executeTasks() {
        const tasks = taskRegistrationModule.getRegisteredTasks();
        this.taskResults = await Promise.all(tasks.map(task => task()));
        console.log('All task results:', this.taskResults);
    }
}

const manager = new ConcurrentTaskManager();

const task1 = async () => {
    return new Promise((resolve) => {
        setTimeout(() => {
            console.log('Task 1 done');
            resolve('Task 1 result');
        }, 1000);
    });
};

const task2 = async () => {
    return new Promise((resolve) => {
        setTimeout(() => {
            console.log('Task 2 done');
            resolve('Task 2 result');
        }, 1500);
    });
};

taskRegistrationModule.registerTask(task1);
taskRegistrationModule.registerTask(task2);

manager.executeTasks();

在上述代码中,taskRegistrationModule 是一个闭包模块,用于注册任务。ConcurrentTaskManager 类负责执行这些任务,并收集任务的结果。通过这种方式,我们将任务的注册和执行逻辑进行了分离,使得代码结构更加清晰,易于维护和扩展。

处理并发任务中的错误

错误处理在并发任务中的重要性

在并发处理中,错误处理是至关重要的。如果一个异步任务出现错误,我们需要确保整个并发执行过程能够正确处理这个错误,而不是让错误导致程序崩溃。

基于类对象和闭包模块的错误处理

在我们之前的代码基础上,我们可以添加错误处理机制。对于 Promise,我们可以使用 .catch() 方法来捕获错误。在类对象和闭包模块中,我们可以通过合适的设计来统一处理任务执行过程中的错误。

例如,修改 ConcurrentTaskManager 类的 executeTasks 方法来处理错误:

class ConcurrentTaskManager {
    constructor() {
        this.taskResults = [];
    }

    async executeTasks() {
        const tasks = taskRegistrationModule.getRegisteredTasks();
        try {
            this.taskResults = await Promise.all(tasks.map(task => task()));
            console.log('All task results:', this.taskResults);
        } catch (error) {
            console.error('An error occurred during task execution:', error);
        }
    }
}

在这个例子中,我们使用 try...catch 块来捕获 Promise.all 执行过程中可能出现的错误。当任何一个任务抛出错误时,catch 块会捕获到这个错误并进行处理,从而保证程序不会因为一个任务的错误而崩溃。

优化并发处理性能

控制并发任务数量

在处理大量并发任务时,同时执行过多的任务可能会导致性能问题,例如资源耗尽或网络拥塞。我们可以通过控制并发任务的数量来优化性能。

例如,我们可以修改 ConcurrentTaskManager 类,使其能够限制同时执行的任务数量:

class ConcurrentTaskManager {
    constructor(maxConcurrent) {
        this.taskResults = [];
        this.maxConcurrent = maxConcurrent;
        this.runningTasks = 0;
    }

    async executeTasks() {
        const tasks = taskRegistrationModule.getRegisteredTasks();
        const taskQueue = [...tasks];
        const results = [];

        const executeNextTask = async () => {
            if (taskQueue.length === 0 && this.runningTasks === 0) {
                console.log('All tasks completed');
                console.log('All task results:', results);
                return;
            }

            while (this.runningTasks < this.maxConcurrent && taskQueue.length > 0) {
                const task = taskQueue.shift();
                this.runningTasks++;
                try {
                    const result = await task();
                    results.push(result);
                } catch (error) {
                    console.error('An error occurred during task execution:', error);
                } finally {
                    this.runningTasks--;
                    executeNextTask();
                }
            }
        };

        await executeNextTask();
    }
}

const manager = new ConcurrentTaskManager(2);

// 注册任务...

manager.executeTasks();

在上述代码中,maxConcurrent 表示允许同时执行的最大任务数量。runningTasks 用于跟踪当前正在执行的任务数量。executeNextTask 函数会在有任务可执行且当前执行任务数量未达到上限时,从任务队列中取出任务并执行。通过这种方式,我们可以有效地控制并发任务的数量,提高性能。

任务优先级处理

在某些场景下,我们可能需要为不同的任务设置优先级,优先执行高优先级的任务。我们可以在类对象和闭包模块的设计中添加任务优先级的支持。

例如,修改 taskRegistrationModuleConcurrentTaskManager 来支持任务优先级:

const taskRegistrationModule = (function () {
    const registeredTasks = [];

    function registerTask(task, priority = 0) {
        registeredTasks.push({ task, priority });
    }

    function getRegisteredTasks() {
        return registeredTasks.sort((a, b) => b.priority - a.priority).map(item => item.task);
    }

    return {
        registerTask,
        getRegisteredTasks
    };
})();

class ConcurrentTaskManager {
    constructor(maxConcurrent) {
        this.taskResults = [];
        this.maxConcurrent = maxConcurrent;
        this.runningTasks = 0;
    }

    async executeTasks() {
        const tasks = taskRegistrationModule.getRegisteredTasks();
        const taskQueue = [...tasks];
        const results = [];

        const executeNextTask = async () => {
            if (taskQueue.length === 0 && this.runningTasks === 0) {
                console.log('All tasks completed');
                console.log('All task results:', results);
                return;
            }

            while (this.runningTasks < this.maxConcurrent && taskQueue.length > 0) {
                const task = taskQueue.shift();
                this.runningTasks++;
                try {
                    const result = await task();
                    results.push(result);
                } catch (error) {
                    console.error('An error occurred during task execution:', error);
                } finally {
                    this.runningTasks--;
                    executeNextTask();
                }
            }
        };

        await executeNextTask();
    }
}

const manager = new ConcurrentTaskManager(2);

const highPriorityTask = async () => {
    return new Promise((resolve) => {
        setTimeout(() => {
            console.log('High priority task done');
            resolve('High priority task result');
        }, 1000);
    });
};

const lowPriorityTask = async () => {
    return new Promise((resolve) => {
        setTimeout(() => {
            console.log('Low priority task done');
            resolve('Low priority task result');
        }, 1500);
    });
};

taskRegistrationModule.registerTask(highPriorityTask, 10);
taskRegistrationModule.registerTask(lowPriorityTask, 5);

manager.executeTasks();

在上述代码中,taskRegistrationModuleregisterTask 方法增加了 priority 参数,用于设置任务的优先级。getRegisteredTasks 方法会根据优先级对任务进行排序。ConcurrentTaskManagerexecuteTasks 方法保持不变,但是由于任务已经按优先级排序,高优先级的任务会优先执行。

并发处理在实际项目中的应用场景

网络请求并发

在前端开发中,经常需要同时发起多个网络请求,例如获取用户信息、商品列表等。通过并发处理,可以提高数据获取的效率,减少用户等待时间。

例如,使用 fetch 进行多个网络请求的并发处理:

const taskRegistrationModule = (function () {
    const registeredTasks = [];

    function registerTask(task) {
        registeredTasks.push(task);
    }

    function getRegisteredTasks() {
        return registeredTasks;
    }

    return {
        registerTask,
        getRegisteredTasks
    };
})();

class ConcurrentTaskManager {
    constructor(maxConcurrent) {
        this.taskResults = [];
        this.maxConcurrent = maxConcurrent;
        this.runningTasks = 0;
    }

    async executeTasks() {
        const tasks = taskRegistrationModule.getRegisteredTasks();
        const taskQueue = [...tasks];
        const results = [];

        const executeNextTask = async () => {
            if (taskQueue.length === 0 && this.runningTasks === 0) {
                console.log('All tasks completed');
                console.log('All task results:', results);
                return;
            }

            while (this.runningTasks < this.maxConcurrent && taskQueue.length > 0) {
                const task = taskQueue.shift();
                this.runningTasks++;
                try {
                    const result = await task();
                    results.push(result);
                } catch (error) {
                    console.error('An error occurred during task execution:', error);
                } finally {
                    this.runningTasks--;
                    executeNextTask();
                }
            }
        };

        await executeNextTask();
    }
}

const manager = new ConcurrentTaskManager(3);

const userInfoTask = async () => {
    const response = await fetch('https://example.com/api/user');
    return response.json();
};

const productListTask = async () => {
    const response = await fetch('https://example.com/api/products');
    return response.json();
};

taskRegistrationModule.registerTask(userInfoTask);
taskRegistrationModule.registerTask(productListTask);

manager.executeTasks();

在这个例子中,我们通过 fetch 发起两个网络请求,并使用 ConcurrentTaskManager 来管理并发执行,控制同时请求的数量,提高请求效率。

数据处理并发

在后端开发或数据处理场景中,可能需要对大量数据进行并行处理。例如,对一批图片进行处理,或者对数据库中的大量记录进行计算等。

例如,模拟对一批数据进行并行计算:

const taskRegistrationModule = (function () {
    const registeredTasks = [];

    function registerTask(task) {
        registeredTasks.push(task);
    }

    function getRegisteredTasks() {
        return registeredTasks;
    }

    return {
        registerTask,
        getRegisteredTasks
    };
})();

class ConcurrentTaskManager {
    constructor(maxConcurrent) {
        this.taskResults = [];
        this.maxConcurrent = maxConcurrent;
        this.runningTasks = 0;
    }

    async executeTasks() {
        const tasks = taskRegistrationModule.getRegisteredTasks();
        const taskQueue = [...tasks];
        const results = [];

        const executeNextTask = async () => {
            if (taskQueue.length === 0 && this.runningTasks === 0) {
                console.log('All tasks completed');
                console.log('All task results:', results);
                return;
            }

            while (this.runningTasks < this.maxConcurrent && taskQueue.length > 0) {
                const task = taskQueue.shift();
                this.runningTasks++;
                try {
                    const result = await task();
                    results.push(result);
                } catch (error) {
                    console.error('An error occurred during task execution:', error);
                } finally {
                    this.runningTasks--;
                    executeNextTask();
                }
            }
        };

        await executeNextTask();
    }
}

const manager = new ConcurrentTaskManager(5);

const data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

const calculateTask = (num) => async () => {
    return new Promise((resolve) => {
        setTimeout(() => {
            const result = num * num;
            console.log(`Calculated ${num}: ${result}`);
            resolve(result);
        }, 500);
    });
};

data.forEach(num => {
    taskRegistrationModule.registerTask(calculateTask(num));
});

manager.executeTasks();

在这个例子中,我们对数组中的每个元素创建一个计算任务,并使用 ConcurrentTaskManager 来并发执行这些任务,提高数据处理的效率。

通过上述对 JavaScript 基于类对象和闭包模块的并发处理的详细介绍,我们可以看到如何利用这些特性来有效地管理和执行并发任务,处理错误,优化性能,并在实际项目中应用。这些技术对于提高 JavaScript 应用程序的效率和响应性具有重要意义。