it('completes the current task before cancelling, and can be resumed', function() { let pipeline; class SometimesBuildCanceller extends plugins.Noop { build() { super.build(); // cancel the first build, but allow the rest to proceed if (this.buildCount === 1 || this.buildCount === 3) { pipeline.cancel(); } } } const stepA = new SometimesBuildCanceller(); const stepB = new plugins.Noop([stepA]); const stepC = new plugins.Noop([stepB]); pipeline = new Builder(stepC); // build #1 let build = pipeline.build(); // once the build has begun: // 1. allow StepA to complete // 2. cancel the build // 3. wait for the build settle // (stepB and stepC should not have run) return expect( promiseFinally(build, () => { expect(stepA.buildCount).to.eql(1, 'stepA.buildCount'); expect(stepB.buildCount).to.eql(0, 'stepB.buildCount'); expect(stepC.buildCount).to.eql(0, 'stepC.buildCount'); }) ) .to.eventually.be.rejectedWith('Build Canceled') .then(() => { // build #2 let build = pipeline.build(); return build.then(() => { expect(stepA.buildCount).to.eql(2, 'stepA.buildCount'); expect(stepB.buildCount).to.eql(1, 'stepB.buildCount'); expect(stepC.buildCount).to.eql(1, 'stepC.buildCount'); // build #3 return expect(pipeline.build()) .to.eventually.be.rejectedWith('Build Canceled') .then(() => { // build will cancel again during stepA (before the stepB) so // only stepA should have made progress expect(stepA.buildCount).to.eql(3, 'stepA.buildCount'); expect(stepB.buildCount).to.eql(1, 'stepB.buildCount'); expect(stepC.buildCount).to.eql(1, 'stepC.buildCount'); }); }); }); });
promise = promise.then(() => { let needsEndNode = false; // We use a nested .then/.catch so that the .catch can only catch errors // from this node, but not from previous nodes. return promiseFinally( Promise.resolve() .then(() => this._cancelationRequest.throwIfRequested()) .then(() => { this.emit('beginNode', nw); needsEndNode = true; }) .then(() => nw.build()), () => { if (needsEndNode) { this.emit('endNode', nw); } } ) .then(() => this._cancelationRequest.throwIfRequested()) .catch(err => { if (Cancelation.isCancelationError(err)) { throw err; } else { throw new BuildError(err, nw); } }); });
function isPersistent(options) { const builder = new FixtureBuilder(new BuildOncePlugin(options)); return promiseFinally( builder .build() .then(() => builder.build()) .then(obj => obj['foo.txt'] === 'test'), () => builder.cleanup() ); }
it('it cancels immediately if cancelled immediately after build', function() { const step = new plugins.Deferred(); let pipeline = new Builder(step); step.resolve(); let build = pipeline.build(); pipeline.cancel(); return promiseFinally( Promise.resolve(expect(build).to.eventually.be.rejectedWith('Build Canceled')), () => { expect(step.buildCount).to.eql(0); } ); });
start() { if (this._started) { throw new Error('Watcher.prototype.start() must not be called more than once'); } const promise = new Promise((resolve, reject) => { this.app = this._connect().use(middleware(this._watcher)); this.http = http.createServer(this.app); this.http.listen(this._port, this._host); this.http.on('listening', () => { console.log(`Serving on ${this._url}\n`); resolve(this._watcher.start()); }); this.http.on('error', error => { if (error.code !== 'EADDRINUSE') { throw error; } let message = `Oh snap 😫. It appears a server is already running on ${this._url}\n`; message += `Are you perhaps already running serve in another terminal window?\n`; reject(new Error(message)); }); process.addListener('SIGINT', this._boundStop); process.addListener('SIGTERM', this._boundStop); this._watcher.on('buildSuccess', () => { this.emit('buildSuccess'); messages.onBuildSuccess(this._builder); }); this._watcher.on('buildFailure', () => { this.emit('buildFailure'); messages.onBuildFailure(); }); }); return promiseFinally(promise, () => this.stop()); }
function buildToFixture(node) { const fixtureBuilder = new FixtureBuilder(node); return promiseFinally(fixtureBuilder.build(), fixtureBuilder.cleanup.bind(fixtureBuilder)); }
// Trigger a (re)build. // // Returns a promise that resolves when the build has finished. If there is a // build error, the promise is rejected with a Builder.BuildError instance. // This method will never throw, and it will never be rejected with anything // other than a BuildError. build() { if (this._cancelationRequest) { return Promise.reject(new BuilderError('Cannot start a build if one is already running')); } let promise = Promise.resolve(); this.buildId++; this.nodeWrappers.forEach(nw => { // We use `.forEach` instead of `for` to close nested functions over `nw` // Wipe all buildState objects at the beginning of the build nw.buildState = {}; promise = promise.then(() => { let needsEndNode = false; // We use a nested .then/.catch so that the .catch can only catch errors // from this node, but not from previous nodes. return promiseFinally( Promise.resolve() .then(() => this._cancelationRequest.throwIfRequested()) .then(() => { this.emit('beginNode', nw); needsEndNode = true; }) .then(() => nw.build()), () => { if (needsEndNode) { this.emit('endNode', nw); } } ) .then(() => this._cancelationRequest.throwIfRequested()) .catch(err => { if (Cancelation.isCancelationError(err)) { throw err; } else { throw new BuildError(err, nw); } }); }); }); this._cancelationRequest = new CancelationRequest(promise); return promiseFinally( promise .then(() => { return this.outputNodeWrapper; }) .then(outputNodeWrapper => { this.buildHeimdallTree(outputNodeWrapper); }), () => { let buildsSkipped = this.nodeWrappers.filter(nw => nw.buildState.built === false).length; logger.debug(`Total nodes skipped: ${buildsSkipped} out of ${this.nodeWrappers.length}`); this._cancelationRequest = null; } ); }
// Destructor-like method. Waits on current node to finish building, then cleans up temp directories cleanup() { return promiseFinally(this.cancel(), () => this.builderTmpDirCleanup()); }