Skip to content

Commit 6af3b6b

Browse files
committed
Implement startOver, queueOwnTask and queueOwnTasks
1 parent a536bcb commit 6af3b6b

File tree

2 files changed

+124
-52
lines changed

2 files changed

+124
-52
lines changed

lib/index.js

Lines changed: 81 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -783,6 +783,64 @@ class Generator extends EventEmitter {
783783
});
784784
}
785785

786+
/**
787+
* @private
788+
* Schedule a generator's method on a run queue.
789+
*
790+
* @param {String} name: The method name to schedule.
791+
* @param {TaskOptions} [taskOptions]: options.
792+
*/
793+
queueOwnTask(name, taskOptions = {}) {
794+
const property = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(this), name);
795+
const item = property.value ? property.value : property.get.call(this);
796+
797+
const priority = this._queues[name];
798+
taskOptions = {
799+
...priority,
800+
cancellable: true,
801+
run: false,
802+
...taskOptions
803+
};
804+
805+
// Name points to a function; run it!
806+
if (typeof item === 'function') {
807+
taskOptions.taskName = name;
808+
taskOptions.method = item;
809+
this.queueTask(taskOptions);
810+
return;
811+
}
812+
813+
// Not a queue hash; stop
814+
if (!priority) {
815+
return;
816+
}
817+
818+
this.queueTaskGroup(item, taskOptions);
819+
}
820+
821+
/**
822+
* @private
823+
* Schedule every generator's methods on a run queue.
824+
*
825+
* @param {TaskOptions} [taskOptions]: options.
826+
*/
827+
queueOwnTasks(taskOptions) {
828+
this._running = true;
829+
this._taskStatus = { cancelled: false, timestamp: new Date() };
830+
831+
const methods = Object.getOwnPropertyNames(Object.getPrototypeOf(this));
832+
const validMethods = methods.filter(methodIsValid);
833+
if (!validMethods.length) {
834+
const error = new Error(
835+
'This Generator is empty. Add at least one method for it to run.'
836+
);
837+
this.emit('error', error);
838+
throw error;
839+
}
840+
841+
validMethods.forEach(methodName => this.queueOwnTask(methodName, taskOptions));
842+
}
843+
786844
/**
787845
* Schedule tasks on a run queue.
788846
*
@@ -810,6 +868,7 @@ class Generator extends EventEmitter {
810868
namespace = self.options.namespace;
811869
}
812870

871+
const taskStatus = this._taskStatus;
813872
debug(
814873
`Queueing ${namespace}#${methodName} with options %o`,
815874
_.omit(task, ['method'])
@@ -820,23 +879,18 @@ class Generator extends EventEmitter {
820879
continueQueue => {
821880
debug(`Running ${namespace}#${methodName}`);
822881
self.emit(`method:${methodName}`);
823-
const taskCancelled = task.cancellable && !self._running;
882+
const taskCancelled = task.cancellable && taskStatus.cancelled;
883+
if (taskCancelled) {
884+
continueQueue();
885+
return;
886+
}
824887

825888
runAsync(function() {
826-
if (taskCancelled) {
827-
return Promise.resolve();
828-
}
829-
830889
self.async = () => this.async();
831890
self.runningState = { namespace, queueName, methodName };
832891
return method.apply(self, self.args);
833892
})()
834893
.then(function() {
835-
if (taskCancelled) {
836-
continueQueue();
837-
return;
838-
}
839-
840894
delete self.runningState;
841895
const eventName = `done$${namespace || 'unknownnamespace'}#${methodName}`;
842896
debug(`Emiting event ${eventName}`);
@@ -878,6 +932,19 @@ class Generator extends EventEmitter {
878932
*/
879933
cancelCancellableTasks() {
880934
this._running = false;
935+
this._taskStatus.cancelled = true;
936+
delete this._taskStatus;
937+
}
938+
939+
/**
940+
* Start the generator again.
941+
*
942+
* @param {Object} [options]: options.
943+
*/
944+
startOver(options = {}) {
945+
this.cancelCancellableTasks();
946+
Object.assign(this.options, options);
947+
this.queueOwnTasks();
881948
}
882949

883950
/**
@@ -933,8 +1000,6 @@ class Generator extends EventEmitter {
9331000
}
9341001

9351002
const promise = new Promise((resolve, reject) => {
936-
const self = this;
937-
this._running = true;
9381003
this.debug('Generator is starting');
9391004
this.emit('run');
9401005

@@ -948,52 +1013,15 @@ class Generator extends EventEmitter {
9481013
this.on('error', reject);
9491014
}
9501015

951-
const methods = Object.getOwnPropertyNames(Object.getPrototypeOf(this));
952-
const validMethods = methods.filter(methodIsValid);
953-
if (!validMethods.length) {
954-
return this.emit(
955-
'error',
956-
new Error('This Generator is empty. Add at least one method for it to run.')
957-
);
958-
}
959-
9601016
this.env.runLoop.once('end', () => {
9611017
this.debug('Generator has ended');
9621018
this.emit('end');
9631019
resolve();
9641020
});
9651021

966-
function addInQueue(name) {
967-
const property = Object.getOwnPropertyDescriptor(
968-
Object.getPrototypeOf(self),
969-
name
970-
);
971-
const item = property.value ? property.value : property.get.call(self);
972-
973-
const priority = self._queues[name];
974-
let taskOptions = {
975-
...priority,
976-
cancellable: true,
977-
run: false,
978-
generatorReject: usePromise ? undefined : reject
979-
};
980-
981-
// Name points to a function; run it!
982-
if (typeof item === 'function') {
983-
taskOptions.taskName = name;
984-
taskOptions.method = item;
985-
return self.queueTask(taskOptions);
986-
}
987-
988-
// Not a queue hash; stop
989-
if (!priority) {
990-
return;
991-
}
992-
993-
self.queueTaskGroup(item, taskOptions);
994-
}
995-
996-
validMethods.forEach(addInQueue);
1022+
this.queueOwnTasks({
1023+
generatorReject: usePromise ? undefined : reject
1024+
});
9971025

9981026
const writeFiles = () => {
9991027
this.env.runLoop.add('conflicts', this._writeFiles.bind(this), {
@@ -1016,6 +1044,7 @@ class Generator extends EventEmitter {
10161044
});
10171045

10181046
this._composedWith.forEach(runGenerator);
1047+
this._composedWith = [];
10191048
});
10201049

10211050
// For composed generators, otherwise error will not be catched.

test/base.js

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -556,6 +556,49 @@ describe('Base', () => {
556556

557557
this.testGen.run().then(done);
558558
});
559+
560+
it('can start over the generator', function(done) {
561+
const spy1 = sinon.spy();
562+
const spy2 = sinon.spy();
563+
564+
this.TestGenerator.prototype.cancel = function() {
565+
spy1();
566+
if (!this.startedOver) {
567+
this.startOver({ startedOver: true });
568+
this.startedOver = true;
569+
}
570+
};
571+
572+
this.TestGenerator.prototype.after = function() {
573+
assert(this.options.startedOver);
574+
assert(this.startedOver);
575+
spy2();
576+
};
577+
578+
this.testGen.run().then(() => {
579+
assert(spy1.calledTwice);
580+
assert(spy2.calledOnce);
581+
done();
582+
});
583+
});
584+
585+
it('can queue a method again', function(done) {
586+
const spy1 = sinon.spy();
587+
588+
this.TestGenerator.prototype.cancel = function() {
589+
spy1();
590+
if (!this.startedOver) {
591+
this.queueOwnTask('cancel');
592+
this.startOver();
593+
this.startedOver = true;
594+
}
595+
};
596+
597+
this.testGen.run().then(() => {
598+
assert(spy1.calledTwice);
599+
done();
600+
});
601+
});
559602
});
560603

561604
describe('#argument()', () => {

0 commit comments

Comments
 (0)