Skip to content

Commit

Permalink
Merge v0.8 branch
Browse files Browse the repository at this point in the history
  • Loading branch information
kriskowal committed Dec 4, 2012
2 parents 32d960c + 4a4a4a1 commit 6f6b600
Show file tree
Hide file tree
Showing 3 changed files with 179 additions and 12 deletions.
29 changes: 22 additions & 7 deletions q.js
Original file line number Diff line number Diff line change
Expand Up @@ -821,12 +821,24 @@ function when(value, fulfilled, rejected, progressed) {
});

// Progress propagator need to be attached in the current tick.
resolvedValue.promiseDispatch(void 0, "when", [
void 0,
function (value) {
deferred.notify(_progressed(value));
resolvedValue.promiseDispatch(void 0, "when", [void 0, function (value) {
var newValue;
var threw = false;
try {
newValue = _progressed(value);
} catch (e) {
threw = true;
if (Q.onerror) {
Q.onerror(e);
} else {
throw e;
}
}
]);

if (!threw) {
deferred.notify(newValue);
}
}]);

return deferred.promise;
}
Expand Down Expand Up @@ -1196,7 +1208,7 @@ function fin(promise, callback) {
*/
Q.done = done;
function done(promise, fulfilled, rejected, progress) {
function onUnhandledError(error) {
var onUnhandledError = function (error) {
// forward to a future turn so that ``when``
// does not catch it and turn it into a rejection.
nextTick(function () {
Expand All @@ -1208,13 +1220,16 @@ function done(promise, fulfilled, rejected, progress) {
throw error;
}
});
}
};

// Avoid unnecessary `nextTick`ing via an unnecessary `when`.
var promiseToHandle = fulfilled || rejected || progress ?
when(promise, fulfilled, rejected, progress) :
promise;

if (typeof process === "object" && process && process.domain) {
onUnhandledError = process.domain.bind(onUnhandledError);
}
fail(promiseToHandle, onUnhandledError);
}

Expand Down
71 changes: 68 additions & 3 deletions spec/lib/jasmine-promise.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,29 @@
"use strict";

/**
* Modifies the way that individual specs are run to easily test async
* code with promises.
*
* A spec may return a promise. If it does, then the spec passes if and
* only if that promise is fulfilled within a very short period of time.
* If it is rejected, or if it isn't fulfilled quickly, the spec fails.
*
* In this way, we can use promise chaining to structure our asynchronous
* tests. Expectations all down the chain of promises are all checked and
* guaranteed to be run and resolved or the test fails.
*
* This is a big win over the runs() and watches() code that jasmine
* supports out of the box.
*/
jasmine.Block.prototype.execute = function (onComplete) {
var spec = this.spec;
var result;
try {
result = this.func.call(spec, onComplete);
var result = this.func.call(spec, onComplete);

// It seems Jasmine likes to return the suite if you pass it anything.
// So make sure it's a promise first.
if (result && typeof result.then === "function") {
result.then(function () {
Q.timeout(result, 500).then(function () {
onComplete();
}, function (error) {
spec.fail(error);
Expand All @@ -24,3 +38,54 @@ jasmine.Block.prototype.execute = function (onComplete) {
}
};

/**
* Tests and documents the behavior of the above extension to jasmine.
*/
describe('jasmine-promise', function() {
it('passes if the deferred resolves immediately', function() {
var deferred = Q.defer();
deferred.resolve();
return deferred.promise;
});
it('passes if the deferred resolves after a short delay', function() {
var deferred = Q.defer();
setTimeout(function() {deferred.resolve();}, 100);
return deferred.promise;
});
it('lets specs that return nothing pass', function() {

});
it('lets specs that return non-promises pass', function() {
return {'some object': 'with values'};
});
it('works ok with specs that return crappy non-Q promises', function() {
return {
'then': function(callback) {
callback();
}
}
});
// These are expected to fail. Remove the x from xdescribe to test that.
xdescribe('failure cases (expected to fail)', function() {
it('fails if the deferred is rejected', function() {
var deferred = Q.defer();
deferred.reject();
return deferred.promise;
});
it('fails if the deferred takes too long to resolve', function() {
var deferred = Q.defer();
setTimeout(function() {deferred.resolve()}, 5 * 1000);
return deferred.promise;
});
it('fails if a returned crappy non-Q promise is rejected', function() {
return {
'then': function(_, callback) {callback()}
}
});
it('fails if a returned crappy promise is never resolved', function() {
return {
'then': function() {}
}
});
})
});
91 changes: 89 additions & 2 deletions spec/q-spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,25 @@ describe("progress", function () {
return promise;
});

it("should re-throw all errors thrown by listeners to Q.onerror", function () {
var theError = new Error("boo!");

var def = Q.defer();
def.promise.progress(function () {
throw theError;
});

var deferred = Q.defer();
Q.onerror = function (error) {
expect(error).toBe(theError);
deferred.resolve();
};
Q.delay(100).then(deferred.reject);

def.notify();

return deferred.promise;
});
});

describe("promises for objects", function () {
Expand Down Expand Up @@ -928,6 +947,33 @@ describe("propagation", function () {

return promise;
});


it("should stop progress propagation if an error is thrown", function () {
var def = Q.defer();
var p2 = def.promise.progress(function () {
throw new Error("boo!");
});

Q.onerror = function () { /* just swallow it for this test */ };

var progressValues = [];
var result = p2.then(
function () {
expect(progressValues).toEqual([]);
},
function () {
expect(true).toBe(false);
},
function (progressValue) {
progressValues.push(progressValue);
}
);

def.notify();
def.resolve();
return result;
});
});

describe("all", function () {
Expand Down Expand Up @@ -1701,11 +1747,21 @@ if (typeof require === "function") {
} catch (e) { }

if (domain) {
var EventEmitter = require("events").EventEmitter;

describe("node domain support", function () {
var d;

beforeEach(function () {
d = domain.create();
});
afterEach(function() {
d.dispose();
});

it("should work for non-promise async inside a promise handler",
function (done) {
var error = new Error("should be caught by the domain");
var d = domain.create();

d.run(function () {
Q.resolve().then(function () {
Expand All @@ -1729,7 +1785,6 @@ if (typeof require === "function") {
it("should transfer errors from `done` into the domain",
function (done) {
var error = new Error("should be caught by the domain");
var d = domain.create();

d.run(function () {
Q.reject(error).done();
Expand All @@ -1745,6 +1800,38 @@ if (typeof require === "function") {
done();
});
});

it("should take care of re-used event emitters", function (done) {
// See discussion in https://github.com/kriskowal/q/issues/120
var error = new Error("should be caught by the domain");

var e = new EventEmitter();

d.run(function () {
callAsync().done();
});
setTimeout(function () {
e.emit("beep");
}, 100);

var errorTimeout = setTimeout(function () {
done(new Error("Wasn't caught"));
}, 500);

d.on("error", function (theError) {
expect(theError).toBe(error);
clearTimeout(errorTimeout);
done();
});

function callAsync() {
var def = Q.defer();
e.once("beep", function () {
def.reject(error);
});
return def.promise;
}
});
});
}
}
Expand Down

0 comments on commit 6f6b600

Please sign in to comment.