diff --git a/examples/taskqueuer.js b/examples/taskqueuer.js new file mode 100644 index 000000000..64925ae69 --- /dev/null +++ b/examples/taskqueuer.js @@ -0,0 +1,40 @@ +/* + * This examples show the usage of the task queue class in order to help + * make your code look cleaner when running lots of nested async tasks. + * + * This is helpful to preventing "pyramid of doom"-style callback nesting. + */ +const mineflayer = require('mineflayer') + +if (process.argv.length < 4 || process.argv.length > 6) { + console.log('Usage : node taskqueuer.js [] []') + process.exit(1) +} + +const bot = mineflayer.createBot({ + host: process.argv[2], + port: parseInt(process.argv[3]), + username: process.argv[4] ? process.argv[4] : 'TaskQueueBot', + password: process.argv[5] +}) + +bot.on('spawn', () => { + const queue = new mineflayer.TaskQueue() + const entity = bot.player.entity + + // Through the use of a task queuer, we can add a lot of nested actions to the queue + // Each task is called inside the callback of the previous task. + + queue.add(cb => setTimeout(cb, 5000)) // Works on any async function + queue.addSync(() => bot.chat('Going to mine down 10 blocks')) // Support for sync functions, too. + + for (let i = 1; i <= 10; i++) { + queue.add(cb => setTimeout(cb, 1000)) + queue.add(cb => bot.dig(bot.blockAt(entity.position.offset(0, -1, 0)), cb)) // Make sure to pass the cb into the callback! + queue.add(cb => setTimeout(cb, 50)) + queue.addSync(() => bot.chat(`Mined block ${i}`)) + } + + // After making the queue, call 'runAll' to execute it. You can add an optional callback when it's done. + queue.runAll(() => bot.chat('All done!')) +}) diff --git a/index.d.ts b/index.d.ts index 93f348d68..691909e6e 100644 --- a/index.d.ts +++ b/index.d.ts @@ -768,3 +768,10 @@ export var supportedVersions: Array; export var testedVersions: Array; export function supportFeature(feature: string, version: string): boolean; + +export class TaskQueue { + stopOnError: boolean; + add(cb: (cb: () => void) => void): void; + addSync(cb: () => void): void; + runAll(cb: (err?: Error) => void): void; +} \ No newline at end of file diff --git a/index.js b/index.js index f808ad804..0d88c80f5 100644 --- a/index.js +++ b/index.js @@ -48,6 +48,7 @@ module.exports = { Dispenser: require('./lib/dispenser'), EnchantmentTable: require('./lib/enchantment_table'), ScoreBoard: require('./lib/scoreboard'), + TaskQueue: require('./lib/taskqueue'), BossBar: require('./lib/bossbar'), supportedVersions, testedVersions, diff --git a/lib/taskqueue.js b/lib/taskqueue.js new file mode 100644 index 000000000..bef653a42 --- /dev/null +++ b/lib/taskqueue.js @@ -0,0 +1,74 @@ +/** + * A utility class for queuing up a set of actions to preform asynchronously. + */ +class TaskQueue { + constructor () { + this._tasks = [] + this.stopOnError = true + } + + /** + * Adds a new task to the end of this task queue. + * + * @param task - The async task to run. + */ + add (task) { + this._tasks.push(task) + } + + /** + * Adds a new sync task to the end of this task queue. + * + * @param task - The sync task to run. + */ + addSync (task) { + this._tasks.push(cb => { + try { + task() + } catch (err) { + cb(err) + return + } + + cb() + }) + } + + /** + * Runs all tasks currently in this queue and empties the queue. + * + * @param cb - The optional callback to run when all tasks are finished. + */ + runAll (cb) { + const taskList = this._tasks + this._tasks = [] + + if (!cb) cb = () => {} + + let index = -1 + const runNext = () => { + index++ + if (index >= taskList.length) { + cb() + return + } + + try { + taskList[index](err => { + if (err) { + cb(err) + if (this.stopOnError) return + } + + runNext() + }) + } catch (err) { + cb(err) + } + } + + runNext() + } +} + +module.exports = TaskQueue