I had to do a lot of IO stuff on my server running Node JS where I did not really care about the result of the operations. The list of incoming tasks could be huge, so I had to limit the number of tasks that could run at the same time. I try to avoid third party libraries wherever I can, so I made my own method.

It seems to work fine, but could I still improve something?

    const pending_tasks = [];
    const running_tasks = [];
    const identifiers = [];
    
    let concurrency = 1;
    
    /*
    	Set how many tasks will run concurrently.
    */
    function set_concurrency (max) {
    	concurrency = max;
    }
    
    /*
    	Check whether a task with the specified id is already in the queue.
    	Only tasks that were added to the queue with their id parameter defined can be checked against.
    */
    function in_queue (id) {
    	if (!id) { return false; }
    
    	return identifiers.includes(id);
    };
    
    /*
    	Add a task to the list of running tasks.
    */
    function fired (id, task) {
    	if (id) { identifiers.push(id); }
    
    	running_tasks.push(task);
    }
    
    /*
    	Delete a task from the list of running tasks.
    */
    function finished (id, task) {
    	if (id) { identifiers.splice(identifiers.indexOf(id), 1); }
    
    	running_tasks.splice(running_tasks.indexOf(task), 1);
    }
    
    /*
    	Add a task to the queue.
    */
    function pending (id, task, parameters) {
    	pending_tasks.push({
    		'id': id,
    		'task': task,
    		'parameters': parameters
    	});
    }
    
    /*
    	Clean up and queue logic after a task has finished.
    */
    function next_task (id, task) {
    	/*
    		Delete the task from the queue.
    	*/
    	finished(id, task);
    
    	/*
    		If there are still pending tasks in the queue ...
    	*/
    	if (pending_tasks.length > 0) {
    		/*
    			... and there are less tasks running than can run concurrently ...
    		*/
    		if (running_tasks.length < concurrency) {
    			/*
    				... get another task from the pending list ...
    			*/
    			const pending_task = pending_tasks.shift();
    
    			/*
    				... and start it.
    			*/
    			fire_and_forget(
    				pending_task['id'],
    				pending_task['task'], 
    				pending_task['parameters']
    			);
    		}
    	}
    }
    
    /*
    	Add tasks to the queue and have them execute without caring about the result.
    	You should set the number of concurrent tasks with the 'set_concurreny (max)' function, if
    	you wish for more than one task to be executed at a time.
    	If a specific task should be unique and only have one instance of itself running, give it
    	an id and check whether it is already in the queue with the 'in_queue (id) function.'
    */
    async function fire_and_forget (id, task, parameters) {
    	/*
    		If an id is defined, check whether it is already in the queue.
    		You should check this yourself before executing this function to avoid an exception.
    	*/
    	if (in_queue(id)) { throw 'Task with id \'' + id + '\' is already in queue.'; }
    
    	/*
    		If there are less tasks running than can run concurrently, start this task immediately.
    	*/
    	if (running_tasks.length < concurrency) {
    		/*
    			Create a promise and apply the parameters to the task to start it.
    		*/
    		const promised_task = new Promise(function (resolve, reject) {
    			task.apply(this, parameters)
    			.then(() => { resolve(); })
    			.catch(() => { reject(); });
    		});
    
    		/*
    			The task has finished.
    		*/
    		promised_task.then(() => {
    			next_task(id, promised_task);
    		}).catch(() => {
    			next_task(id, promised_task);
    		});
    
    		/*
    			Add the task to the list of running tasks.
    		*/
    		fired(id, promised_task);
    	}
    	else {
    		/*
    			Add the task to the queue.
    		*/
    		pending(id, task, parameters);
    	}
    };
    
    module.exports.set_concurrency = set_concurrency;
    module.exports.in_queue = in_queue;
    module.exports.fire_and_forget = fire_and_forget;
    
    
    
    
    
    
    /*
    	TEST SCENARIO --- Remove code beneath for production
    */
    
    // Dummy task
    async function long_write_task(subject, body) {
    	await new Promise((resolve) => setTimeout(resolve, 1000));
    
    	console.log([subject, body]);
    }
    
    // Set concurrency to 3 tasks.
    set_concurrency(3);
    
    // Start 10 tasks.
    for(let i = 0; i < 10; i++){
    	// Dummy parameter list.
    	const parameters = ['subject ' + i, 'body ' + i];
    
    	// Check if id already queued.
    	// Can't be if we just assign the loop iteration number as id, but is best practice.
    	// If you don't care about running an identical task twice, leave id 'undefined'.
    	if (!in_queue(i)) {
    		fire_and_forget(i, long_write_task, parameters);
    	}
    }


**EDITED CODE**

    const enqueued_ids = [];
    const enqueued_tasks = [];
    
    let running = 0;
    let concurrency = 10;
    
    /*
    	Set how many tasks will run concurrently.
    */
    function set_concurrency (max) {
    	concurrency = max;
    };
    
    /*
    	Check whether a task with the specified id is already in the queue.
    */
    function in_queue (id) {
    	if ([undefined, null].includes(id)) { return false; }
    
    	return enqueued_ids.includes(id);
    };
    
    /*
    	Add the id for a task to the list of enqueued ids.
    */
    function enqueue_id (id) {
    	if ([undefined, null].includes(id)) { return; }
    
    	enqueued_ids.push(id);
    };
    
    /*
    	Remove the id after the task has finished.
    */
    function dequeue_id (id) {
    	if ([undefined, null].includes(id)) { return; }
    
    	enqueued_ids.splice(enqueued_ids.indexOf(id), 1);
    };
    
    /*
    	Start a new task from the queue.
    */
    function fire_task () {
    	/*
    		If there are pending tasks in the queue ...
    	*/
    	if (enqueued_tasks.length > 0) {
    		/*
    			... and there are less tasks running than can run concurrently ...
    		*/
    		if (running < concurrency) {
    			/*
    				... get a task from the queue ...
    			*/
    			const enqueued_task = enqueued_tasks.shift();
    			const id = enqueued_task['id'];
    			const task = enqueued_task['task'];
    
    			/*
    				... and start it.
    			*/
    			const fired_task = new Promise(function (resolve, reject) {
    				running++;
    
    				task()
    				.then(() => { resolve(); })
    				.catch(() => { reject(); });
    			});
    
    			/*
    				The task has finished.
    			*/
    			fired_task.then(() => {
    				running--;
    
    				dequeue_id(id);
    				fire_task();
    			}).catch(() => {
    				running--;
    
    				dequeue_id(id);
    				fire_task();
    			});
    		}
    	}
    };
    
    /*
    	Add tasks to the queue and have them execute without caring about the result.
    	You should set the number of concurrent tasks with the 'set_concurreny (max)' function, if
    	you wish for more than one task to be executed at a time.
    	If a specific task should be unique and only have one instance of itself running, give it
    	an id and check whether it is already in the queue with the 'in_queue (id)' function.
    */
    async function fire_and_forget (id, task) {
    	/*
    		If an id is defined, check whether it is already in the queue.
    		You should check this yourself before executing this function to avoid an exception.
    	*/
    	if (in_queue(id)) { throw 'Task with id \'' + id + '\' is already in queue.'; }
    
    	/*
    		If the id is valid, add it to the list of enqueued ids.
    	*/
    	enqueue_id(id);
    
    	/*
    		Bind the parameters to the task.
    	*/
    	const bound_task = task.bind.apply(task, [null].concat([].slice.call(arguments).slice(2)));
    
    	/*
    		Add the task to the queue.
    	*/
    	enqueued_tasks.push({
    		'id': id,
    		'task': bound_task
    	});
    
    	/*
    		Start a new task from the queue.
    	*/
    	fire_task();
    };
    
    module.exports.set_concurrency = set_concurrency;
    module.exports.in_queue = in_queue;
    module.exports.fire_and_forget = fire_and_forget;
    
    /*
        TEST SCENARIO --- Remove code beneath for production
    */
    
    // Dummy task
    async function long_write_task(subject, body) {
        await new Promise((resolve) => setTimeout(resolve, 1000));
    
        console.log([subject, body]);
    };
    
    
    // Set concurrency to 3 tasks.
    set_concurrency(3);
    
    // Start 10 tasks.
    for(let i = 0; i < 10; i++){
        // Check if id already queued.
        // Can't be if we just assign the loop iteration number as id, but is best practice.
        // If you don't care about running an identical task twice, leave id 'undefined'.
        if (!in_queue(i)) {
            fire_and_forget(i, long_write_task, 'subject ' + i, 'body ' + i);
        }
    }