Skip to content

Commit

Permalink
Don't swallow exceptions throw in async validators
Browse files Browse the repository at this point in the history
This closes ansman#10
  • Loading branch information
ansman committed Apr 7, 2015
1 parent e58b1de commit db913c9
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 12 deletions.
23 changes: 18 additions & 5 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -719,15 +719,16 @@ <h3>Async validation</h3>
and accepts the same options.
</p>
<p class="description">
<code>validate.async</code> returns a <a href="http://www.html5rocks.com/en/tutorials/es6/promises/" target="_blank">Promise</a>
<code>validate.async</code> returns a
<a href="http://www.html5rocks.com/en/tutorials/es6/promises/" target="_blank">Promise</a>
that is resolved if the validation passes and is rejected if the
validation failed, passing the errors as the first argument.<br>
The errors has the same format as the errors from the regular validation function.
</p>
<p class="description">
The errors object will be an instance of <b>validate.ValidationErrors</b>
to be able to differentiate between coding errors and validation
errors.
If an <code>Error</code> is thrown from an async validator the
argument passed to the rejection handler will be that error. This
allows you to differentiate from coding errors and validation errors.
</p>
<p class="description">
You can use the async validate function even if no validations
Expand All @@ -751,7 +752,12 @@ <h3>Async validation</h3>
alert("The validations passed");
}
, error = function(errors) {
alert(JSON.stringify(errors, null, 2));
if (errors instanceof Error) {
// This means an exception was thrown from a validator
console.err(errors);
} else {
alert(JSON.stringify(errors, null, 2));
}
}
, constraints = {name: {presence: true}};

Expand Down Expand Up @@ -1947,6 +1953,13 @@ <h3>
<b>Breaking:</b> Don't supply a name when calling <code>define</code>.
Thanks <a href="https://github.com/ansman/validate.js/issues/20" target="_blank">Norman Xu</a> for reporting this.
</li>
<li>
<b>Breaking:</b> If a async validator throw an error (or rejects
the promise with an <code>Error</code>) the validation promise
is rejected with the same error. Thanks
<a href="https://github.com/ansman/validate.js/issues/10" target="_blank">Sebastian Seilund, Dmitry Kirilyuk and Dave Kushner</a>
for helping out with the details of this.
</li>
</ul>
</div>
<div id="changelog-0-6-1">
Expand Down
40 changes: 39 additions & 1 deletion specs/validate-async-spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ describe("validate.async", function() {
afterEach(function() {
delete validate.validators.asyncFail;
delete validate.validators.asyncSuccess;
delete validate.validators.asyncError;
delete validate.async.options;
validate.Promise = Promise;
});
Expand Down Expand Up @@ -67,7 +68,6 @@ describe("validate.async", function() {
});
});


it.promise("handles validators returning a promise", function() {
var c = {
name: {
Expand Down Expand Up @@ -158,6 +158,26 @@ describe("validate.async", function() {
expect(validate.warn).toHaveBeenCalled();
});
});

it.promise("rejects the promise if any promise throw an exception", function() {
var results = [{
attribute: "foo",
error: new validate.Promise(function(res, rej) { res(); })
}, {
attribute: "bar",
error: new validate.Promise(function(resolve, reject) {
throw new Error("Error");
})
}, {
attribute: "baz",
error: new validate.Promise(function(res, rej) { res(); })
}];

return validate.waitForResults(results).then(success, error).then(function() {
expect(success).not.toHaveBeenCalled();
expect(error).toHaveBeenCalledWith(new Error("Error"));
});
});
});

it.promise("allows default options", function() {
Expand All @@ -171,4 +191,22 @@ describe("validate.async", function() {
expect(validate.async.options).toEqual({flatten: true});
});
});

it.promise("rejects the promise with an error if an exception is thrown", function() {
var c = {
attribute: {
asyncError: true
}
};
validate.validators.asyncError = function() {
return new Promise(function(resolve, reject) {
reject(new Error("Some error"));
});
};

return validate.async({}, c).then(success, error).then(function() {
expect(success).not.toHaveBeenCalled();
expect(error).toHaveBeenCalledWith(new Error("Some error"));
});
});
});
14 changes: 8 additions & 6 deletions validate.js
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,9 @@
} else {
resolve(attributes);
}
}).then(undefined, v.error);
}, function(err) {
reject(err);
});
});
},

Expand All @@ -196,7 +198,7 @@
// with the value returned from the promise.
waitForResults: function(results) {
// Create a sequence of all the results starting with a resolved promise.
var promise = results.reduce(function(memo, result) {
return results.reduce(function(memo, result) {
// If this result isn't a promise skip it in the sequence.
if (!v.isPromise(result.error)) {
return memo;
Expand All @@ -212,14 +214,14 @@
// error was specified.
if (!error) {
v.warn("Validator promise was rejected but didn't return an error");
} else if (error instanceof Error) {
throw error;
}
result.error = error;
}
).then(undefined, v.error);
}).then(undefined, v.error);
);
});
}, new v.Promise(function(r) { r(); })); // A resolved promise

return promise.then(undefined, v.error);
},

// If the given argument is a call: function the and: function return the value
Expand Down

0 comments on commit db913c9

Please sign in to comment.