Strange/unexpected jQuery promise reject behaviour - javascript

I am experimenting some strange behaviours with jQuery promises on reject.
I have an array of promises, and I need to work with them when they all have been resolved/rejected.
To do so, I am using this:
var array_res = [];
array_res.push(promiseResolve('a'));
array_res.push(promiseReject('b'));
$.when.apply(null,array_res).always( function ( ) {
console.log(arguments);
//Work to do
} );
function promiseResolve (c) {
var promise = $.Deferred();
promise.resolve({a:c});
return promise;
}
function promiseReject (c) {
var promise = $.Deferred();
promise.reject({b:c});
return promise;
}
The issue is:
If I resolve both promises, everything works fine.
If I reject one of the promises, then the arguments come incomplete.
If I reject both of them, then the arguments come incomplete.
Here are 3 fiddles where you can check the behaviour:
https://jsfiddle.net/daepqzv1/1/
https://jsfiddle.net/daepqzv1/2/
https://jsfiddle.net/daepqzv1/3/
What I need is a way to get the arguments for both, rejected and resolved.

This is normal behavior for $.when(). If any promises you pass to $.when() reject, then $.when() will reject with the first reject reason that it finds. This is how it is coded.
This is similar to the way the ES6 Promise.all() works.
If you want all results, even if some promises reject, then you can use something like $.settle() or $.settleVal() that are defined in this code:
(function() {
function isPromise(p) {
return p && (typeof p === "object" || typeof p === "function") && typeof p.then === "function";
}
function wrapInPromise(p) {
if (!isPromise(p)) {
p = $.Deferred().resolve(p);
}
return p;
}
function PromiseInspection(fulfilled, val) {
return {
isFulfilled: function() {
return fulfilled;
}, isRejected: function() {
return !fulfilled;
}, isPending: function() {
// PromiseInspection objects created here are never pending
return false;
}, value: function() {
if (!fulfilled) {
throw new Error("Can't call .value() on a promise that is not fulfilled");
}
return val;
}, reason: function() {
if (fulfilled) {
throw new Error("Can't call .reason() on a promise that is fulfilled");
}
return val;
}
};
}
// pass either multiple promises as separate arguments or an array of promises
$.settle = function(p1) {
var args;
if (Array.isArray(p1)) {
args = p1;
} else {
args = Array.prototype.slice.call(arguments);
}
return $.when.apply($, args.map(function(p) {
// make sure p is a promise (it could be just a value)
p = wrapInPromise(p);
// Now we know for sure that p is a promise
// Make sure that the returned promise here is always resolved with a PromiseInspection object, never rejected
return p.then(function(val) {
return new PromiseInspection(true, val);
}, function(reason) {
// convert rejected promise into resolved promise by returning a resolved promised
// One could just return the promiseInspection object directly if jQuery was
// Promise spec compliant, but jQuery 1.x and 2.x are not so we have to take this extra step
return wrapInPromise(new PromiseInspection(false, reason));
});
})).then(function() {
// return an array of results which is just more convenient to work with
// than the separate arguments that $.when() would normally return
return Array.prototype.slice.call(arguments);
});
}
// simpler version that just converts any failed promises
// to a resolved value of what is passed in, so the caller can just skip
// any of those values in the returned values array
// Typically, the caller would pass in null or 0 or an empty object
$.settleVal = function(errorVal, p1) {
var args;
if (Array.isArray(p1)) {
args = p1;
} else {
args = Array.prototype.slice.call(arguments, 1);
}
return $.when.apply($, args.map(function(p) {
p = wrapInPromise(p);
return p.then(null, function(err) {
return wrapInPromise(errorVal);
});
}));
}
})();
$.settle() always resolves and it resolves with an array of PromiseInspection objects that you can then iterate over to see which promises resolved and which rejected and what the value or reason was.
$.settleVal() is a little simpler to iterate, but a little less generic because it doesn't give you the reject reason. It always resolves with an array where a reject will have a default value that you pass to it in the array in place of the resolved value.
FYI, both $.settle() and $.settleVal() can both be passed an array of promises like $.settle(arrayOfPromises) or multiple promise arguments as $.settle(p1, p2, p3) (as $.when() works). This saves having to use .apply() when you have an array of promises.

Related

Understanding the Promises/A+ specification

The Promises/A+ specification is one of the smallest specifications. Hence, implementing it is the best way to understand it. The following answer by Forbes Lindesay walks us through the process of implementing the Promises/A+ specification, Basic Javascript promise implementation attempt. However, when I tested it the results were not satisfactory:
✔ 109 tests passed
✘ 769 tests failed
Clearly, the Promises/A+ specification is not as easy to implement as it seems. How would you implement the specification and explain your code to a novice? Forbes Lindesay does an excellent job explaining his code but unfortunately his implementation is incorrect.
What is a promise?
A promise is a thenable whose behavior conforms to the Promises/A+ specification.
A thenable is any object or function that has a then method.
Here's what a promise looks like:
var promise = {
...
then: function (onFulfilled, onRejected) { ... },
...
};
This is the only thing we know about a promise from the outset (excluding its behavior).
Understanding the Promises/A+ specification
The Promises/A+ specification is divided into 3 main parts:
Promise states
The then Method
The Promise Resolution Procedure
The specification does not mention how to create, fulfill or reject promises.
Hence, we'll start by creating those functions:
function deferred() { ... } // returns an object { promise, resolve, reject }
function fulfill(promise, value) { ... } // fulfills promise with value
function reject(promise, reason) { ... } // rejects promise with reason
Although there's no standard way of creating a promise yet the tests require us to expose a deferred function anyway. Hence, we'll only use deferred to create new promises:
deferred(): creates an object consisting of { promise, resolve, reject }:
promise is a promise that is currently in the pending state.
resolve(value) resolves the promise with value.
reject(reason) moves the promise from the pending state to the rejected state, with rejection reason reason.
Here's a partial implementation of the deferred function:
function deferred() {
var call = true;
var promise = {
then: undefined,
...
};
return {
promise: promise,
resolve: function (value) {
if (call) {
call = false;
resolve(promise, value);
}
},
reject: function (reason) {
if (call) {
call = false;
reject(promise, reason);
}
}
};
}
N.B.
The promise object only has a then property which is currently undefined. We still need to decide on what the then function should be and what other properties a promise object should have (i.e. the shape of a promise object). This decision will also affect the implementation of the fulfill and reject functions.
The resolve(promise, value) and reject(promise, value) functions should only be callable once and if we call one then we shouldn't be able to call the other. Hence, we wrap them in a closure and ensure that they are only called once between both of them.
We introduced a new function in the definition of deferred, the promise resolution procedure resolve(promise, value). The specification denotes this function as [[Resolve]](promise, x). The implementation of this function is entirely dictated by the specification. Hence, we'll implement it next.
function resolve(promise, x) {
// 2.3.1. If promise and x refer to the same object,
// reject promise with a TypeError as the reason.
if (x === promise) return reject(promise, new TypeError("Self resolve"));
// 2.3.4. If x is not an object or function, fulfill promise with x.
var type = typeof x;
if (type !== "object" && type !== "function" || x === null)
return fulfill(promise, x);
// 2.3.3.1. Let then be x.then.
// 2.3.3.2. If retrieving the property x.then results in a thrown exception e,
// reject promise with e as the reason.
try {
var then = x.then;
} catch (e) {
return reject(promise, e);
}
// 2.3.3.4. If then is not a function, fulfill promise with x.
if (typeof then !== "function") return fulfill(promise, x);
// 2.3.3.3. If then is a function, call it with x as this, first argument
// resolvePromise, and second argument rejectPromise, where:
// 2.3.3.3.1. If/when resolvePromise is called with a value y,
// run [[Resolve]](promise, y).
// 2.3.3.3.2. If/when rejectPromise is called with a reason r,
// reject promise with r.
// 2.3.3.3.3. If both resolvePromise and rejectPromise are called,
// or multiple calls to the same argument are made,
// the first call takes precedence, and any further calls are ignored.
// 2.3.3.3.4. If calling then throws an exception e,
// 2.3.3.3.4.1. If resolvePromise or rejectPromise have been called, ignore it.
// 2.3.3.3.4.2. Otherwise, reject promise with e as the reason.
promise = deferred(promise);
try {
then.call(x, promise.resolve, promise.reject);
} catch (e) {
promise.reject(e);
}
}
N.B.
We omitted section 2.3.2 because it's an optimization that depends upon the shape of a promise object. We'll revisit this section towards the end.
As seen above, the description of section 2.3.3.3 is much longer than the actual code. This is because of the clever hack promise = deferred(promise) which allows us to reuse the logic of the deferred function. This ensures that promise.resolve and promise.reject are only callable once between both of them. We only need to make a small change to the deferred function to make this hack work.
function deferred(promise) {
var call = true;
promise = promise || {
then: undefined,
...
};
return /* the same object as before */
}
Promise states and the then method
We've delayed the problem of deciding the shape of a promise object for so long but we can't delay any further because the implementations of both the fulfill and reject functions depend upon it. It's time to read what the specification has to say about promise states:
A promise must be in one of three states: pending, fulfilled, or rejected.
When pending, a promise:
may transition to either the fulfilled or rejected state.
When fulfilled, a promise:
must not transition to any other state.
must have a value, which must not change.
When rejected, a promise:
must not transition to any other state.
must have a reason, which must not change.
Here, “must not change” means immutable identity (i.e. ===), but does not imply deep immutability.
How do we know which state the promise is currently in? We could do something like this:
var PENDING = 0;
var FULFILLED = 1;
var REJECTED = 2;
var promise = {
then: function (onFulfilled, onRejected) { ... },
state: PENDING | FULFILLED | REJECTED, // vertical bar is not bitwise or
...
};
However, there's a better alternative. Since the state of a promise is only observable through it's then method (i.e. depending upon the state of the promise the then method behaves differently), we can create three specialized then functions corresponding to the three states:
var promise = {
then: pending | fulfilled | rejected,
...
};
function pending(onFulfilled, onRejected) { ... }
function fulfilled(onFulfilled, onRejected) { ... }
function rejected(onFulfilled, onRejected) { ... }
In addition, we need one more property to hold the data of the promise. When the promise is pending the data is a queue of onFulfilled and onRejected callbacks. When the promise is fulfilled the data is the value of the promise. When the promise is rejected the data is the reason of the promise.
When we create a new promise the initial state is pending and the initial data is an empty queue. Hence, we can complete the implementation of the deferred function as follows:
function deferred(promise) {
var call = true;
promise = promise || {
then: pending,
data: []
};
return /* the same object as before */
}
In addition, now that we know the shape of a promise object we can finally implement the fulfill and reject functions:
function fulfill(promise, value) {
setTimeout(send, 0, promise.data, "onFulfilled", value);
promise.then = fulfilled;
promise.data = value;
}
function reject(promise, reason) {
setTimeout(send, 0, promise.data, "onRejected", reason);
promise.then = rejected;
promise.data = reason;
}
function send(queue, callback, data) {
for (var item of queue) item[callback](data);
}
We need to use setTimeout because according to section 2.2.4 of the specification onFulfilled or onRejected must not be called until the execution context stack contains only platform code.
Next, we need to implement the pending, fulfilled and rejected functions. We'll start with the pending function which pushes the onFulfilled and onRejected callbacks to the queue and returns a new promise:
function pending(onFulfilled, onRejected) {
var future = deferred();
this.data.push({
onFulfilled: typeof onFulfilled === "function" ?
compose(future, onFulfilled) : future.resolve,
onRejected: typeof onRejected === "function" ?
compose(future, onRejected) : future.reject
});
return future.promise;
}
function compose(future, fun) {
return function (data) {
try {
future.resolve(fun(data));
} catch (reason) {
future.reject(reason);
}
};
}
We need to test whether onFulfilled and onRejected are functions because according to section 2.2.1 of the specification they are optional arguments. If onFulfilled and onRejected are provided then they are composed with the deferred value as per section 2.2.7.1 and section 2.2.7.2 of the specification. Otherwise, they are short-circuited as per section 2.2.7.3 and section 2.2.7.4 of the specification.
Finally, we implement the fulfilled and rejected functions as follows:
function fulfilled(onFulfilled, onRejected) {
return bind(this, onFulfilled);
}
function rejected(onFulfilled, onRejected) {
return bind(this, onRejected);
}
function bind(promise, fun) {
if (typeof fun !== "function") return promise;
var future = deferred();
setTimeout(compose(future, fun), 0, promise.data);
return future.promise;
}
Interestingly, promises are monads as can be seen in the aptly named bind function above. With this, our implementation of the Promises/A+ specification is now complete.
Optimizing resolve
Section 2.3.2 of the specification describes an optimization for the resolve(promise, x) function when x is determined to be a promise. Here's the optimized resolve function:
function resolve(promise, x) {
if (x === promise) return reject(promise, new TypeError("Self resolve"));
var type = typeof x;
if (type !== "object" && type !== "function" || x === null)
return fulfill(promise, x);
try {
var then = x.then;
} catch (e) {
return reject(promise, e);
}
if (typeof then !== "function") return fulfill(promise, x);
// 2.3.2.1. If x is pending, promise must remain pending until x is
// fulfilled or rejected.
if (then === pending) return void x.data.push({
onFulfilled: function (value) {
fulfill(promise, value);
},
onRejected: function (reason) {
reject(promise, reason);
}
});
// 2.3.2.2. If/when x is fulfilled, fulfill promise with the same value.
if (then === fulfilled) return fulfill(promise, x.data);
// 2.3.2.3. If/when x is rejected, reject promise with the same reason.
if (then === rejected) return reject(promise, x.data);
promise = deferred(promise);
try {
then.call(x, promise.resolve, promise.reject);
} catch (e) {
promise.reject(e);
}
}
Putting it all together
The code is available as a gist. You can simply download it and run the test suite:
$ npm install promises-aplus-tests -g
$ promises-aplus-tests promise.js
Needless to say, all the tests pass.

Promise returned from method gets undefined

I have a method which accepts function as a callback parameter. Both outer method and callback function returns promises which I collect to array, waiting for them to be resolved with Q.all.
function eval() {
var colReadPromisses = [];
config.mongodb.colls.forEach(function (collName) {
var outer = mongo.readCollection(collName,
function (jsonData) {
var prom = es.pushItemToES(jsonData, esIndexName, collName, i);
colReadPromisses.push(prom);
});
colReadPromisses.push(outer);
});
return Q.all(colReadPromisses);
}
Now, inner callback method is called many times and it takes some time to process all of them. While they are being processed, promises returned from 'readCollection' method gets 'undefined' causing 'Q.all(colReadPromisses);' to resolve.
So my two questions would be why nodejs loses track to the promises returned from 'readCollection' method and how do I avoid this?
Thanks for responses!
I'm assuming you want to get the results of all both readCollection and pushItemToES in the returned promise
function eval() {
return Q.all(config.mongodb.colls.map(function (collName) {
var pushPromisses = [mongo.readCollection(collName, function (jsonData) {
pushPromisses.push(es.pushItemToES(jsonData, esIndexName, collName, i));
})];
return pushPromisses[0] // once the outer promise resolves
.then(function() {
return Q.all(pushPromisses); // wait for them all (including the outer)
});
}));
}
The returned promise when resolved, will resolve to a two-dimension array
[0][0] will be the result of the mongo.readCollection(colls[0])
[0][1..n] will be the results of each pushItemToES from readCollection(colls[0])
[1][0] will be the result of themongo.readCollection(colls[1])`
[1][1..n] will be the results of each pushItemToES from readCollection(colls[1])
and so on
if you don't need the result of mongo.readCollection in the returned promise
function eval() {
return Q.all(config.mongodb.colls.map(function (collName) {
var pushPromisses = [];
return mongo.readCollection(collName, function (jsonData) {
pushPromisses.push(es.pushItemToES(jsonData, esIndexName, collName, i));
}).then(function() {
return Q.all(pushPromisses);
});
}));
}
in this case the returned promise is still two dimensional, but with just the results of the pushItemToES
[0][0..n] will be the results of each pushItemToES from readCollection(colls[0])
[1][0..n] will be the results of each pushItemToES from readCollection(colls[1])
I can't see any way around the two dimensional array in the resolved promise - because the es.pushItemToES is not called a "known" (at least not ahead of time) number of times
You must reorganize your code:
function eval() {
// replace forEach with map
var colReadPromisses = config.mongodb.colls.map(function (collName) {
// return pushItemToES promises
return new Promise(function(resolve) {
mongo.readCollection(collName,
function (jsonData) {
resolve(es.pushItemToES(jsonData, esIndexName, collName, i));
});
});
});
// colReadPromisses stores array of pushItemToES promises
return Q.all(colReadPromisses);
}

Possible Async Call in a Conditional

I'm refactoring some legacy code that I didn't originally write and I've come upon an issue with asynchronous data loading. The first time a particular modal is opened, a bunch of data representing a form object gets loaded. A function then cycles through the inputs of the form and fleshes them out as needed. It looks something like this (extremely simplified):
component.inputs.forEach(function(input) {
if (input.field == 'foo') {
input.cols = 5;
//etc.
}
if (input.field == 'bar') {
DataService.getBars().then(function(data){
data.forEach(function(e){
input.options.push(e.description);
});
};
}
if (input.field == 'baz') {
input.pattern = /regex/;
//etc.
}
});
return component;
The problem, of course, is that if my input.field is 'bar', the code continues running and hits the final return before the async call to DataService is resolved, so the first time the modal is opened, the input.options have not been filled out for 'bar' input.
Is it possible to make the code wait for the promise from the DataService to be resolved before continuing, or is there another way to handle the situation where in most cases the function is synchronous, but has to make an async call in only one case? Or have I shot myself in the foot by including an async call in this big chain of ifs?
One approach is to create a promise and attach it as a property to your returned object.
function getComponent() {
component.inputs.forEach(function(input) {
//create initial promise
var $promise = $q.when(input);
if (input.field == 'foo') {
input.cols = 5;
//etc.
}
if (input.field == 'bar') {
//chain from initial promise
$promise = $promise.then(function () {
//return promise for chaining
return getBarPromise(input);
});
}
//attach promise to input object
input.$promise = $promise;
});
var promises = [];
angular.forEach(inputs, function(input) {
promises.push(input.$promise);
});
//create composite promise
var $promise = $q.all(promises);
//final chain
$promise = $promise.then( function() {
//return component for chaining
return component;
});
//attach promise to component
component.$promise = $promise;
return component;
};
The returned component object will eventually be filled in with the results of the service calls. Functions that need to wait for completion of all the service calls can chain from the attached $promise property.
$scope.component = getComponent();
$scope.component.$promise.then( function (resolvedComponent) {
//open modal
}).catch( function(errorResponse) {
//log error response
});
Because calling the then method of a promise returns a new derived promise, it is easily possible to create a chain of promises. It is possible to create chains of any length and since a promise can be resolved with another promise (which will defer its resolution further), it is possible to pause/defer resolution of the promises at any point in the chain. This makes it possible to implement powerful APIs.1
If you want to stay with your existing code structure and make this work, you probably will need to use promises. You can also use javascript's map function. Note: you would need to inject $q into wherever you want to call this function.
function getComponent() {
var deferred = $q.defer(),
deferred2 = $q.defer(),
promises = component.inputs.map(function(input)) {
if (input.field == 'foo') {
input.cols = 5;
deferred2.resolve();
}
else if (input.field == 'bar') {
DataService.getBars().then(function(data) {
data.forEach(function(e){
input.options.push(e.description);
});
deferred2.resolve();
}).catch(function(err)) {
deferred2.reject(err);
});
}
else if (input.field == 'baz') {
input.pattern = /regex/;
deferred2.resolve();
}
return deferred2.promise;
});
$q.all(promises)
.then(function() {
deferred.resolve(component);
}).catch(function(err) {
deferred.reject(err);
});
return deferred.promise;
}
Once each input in component.inputs has been parsed appropriately, then the $q.all block will trigger and you can return your new component object.
Finally, to set your component object, simply do the following:
getComponent().then(function(result)) {
//Set component object here with result
$scope.component = result;
}).catch(function(err) {
// Handle error here
});

deferred anti pattern of promises unclear example

from
https://github.com/petkaantonov/bluebird/wiki/Promise-anti-patterns
what promise is he talking about?
myApp.factory('Configurations', function (Restangular, MotorRestangular, $q) {
var getConfigurations = function () {
//Just return the promise we already have!
return MotorRestangular.all('Motors').getList().then(function (Motors) {
//Group by Cofig
var g = _.groupBy(Motors, 'configuration');
//Return the mapped array as the value of this promise
return _.map(g, function (m) {
return {
id: m[0].configuration,
configuration: m[0].configuration,
sizes: _.map(m, function (a) {
return a.sizeMm
})
}
});
});
};
return {
config: getConfigurations()
}
});
where is a promise? for me it looks more like it's an anti pattern to use his pattern. I cannot see any promise in this code apart from the word then nothing makes me think of a promise.
So what does return MotorRestangular... actually return?
One thing to remember is that, both the resolve and reject function return a promise. In other word, it's already promisified for you. Even if you don't explicitly return something from the resolve, what you get is a promise that has been resolved with a value of undefined.
In your example, MotorRestangular.all('Motors').getList() returns a promise and in the resolve function, the first param in then, another resolved promise is returned which will get the result of _.map function as input. It's like:
function YourCtrl(Configurations) {
Configurations.getConfiguration().then(function(resultOfMap) {
// use resultOfMap here
})
}
Note:
Don't get confused by resolved and onFulfilled. They are kinda the same but the former is for deferred object and the latter is from Promise specification.
MotorRestangular.all('Motors').getList()
is a promise returned by MotorRestangular. You can easily chain promises between methods whenever you just make the called function return a promise.

Javascript Promises returning strange object

(function() {
"use strict";
var storage = chrome.storage.sync;
var localStorage = null;
function getOutsideScope(property) {
if (localStorage.hasOwnProperty(property)) {
return localStorage[property];
}
}
function fulfill(data) {
return data;
}
function rejected(err) {
log(err);
}
window.app.storage = {
get: function(storageKey, storageProp) {
var promise = new Promise(function(fullfill, reject) {
chrome.storage.sync.get(storageKey, function(data) {
if (data.hasOwnProperty(storageKey)) {
fullfill(data[storageKey]);
} else {
reject(new Error(storageKey + " does not exist in the storage values."));
}
});
});
return promise.then(fulfill, rejected);
},
set: function(storageKey, storageItem) {
},
onChanged: function(fn) {
}
};
})();
So the above is my IIFE wrapper for chrome storage, and well the return is being a pain in the bleep. So I decided to give Promises a try, this is my first time so don't be too rough on me on this. Basically this is what I want to do
var property = app.storage.get("properties");
//property should equal "value"
//except it returns undefined
So adding the promises it does get the value except it returns this
Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: "value"}
Am I doing something wrong with Promises I've tried reading HTML5Rocks, the MDN, and video tutorials except it doesn't really mention much of how to return a value. This does NOT work
get:function(storageKey,storageProp) {
chrome.storage.sync.get(storageKey,function(data) {
//for simplicity and no error checking -_-
return data[storageKey];
});
}
The function returns exactly what it is supposed to return: A promise. To get the value once the promise is fulfilled, you add a callback via .then:
app.storage.get("properties").then(function(property) {
// property will be "value"
});
From MDN:
A Promise represents a proxy for a value not necessarily known when the promise is created. It allows you to associate handlers to an asynchronous action's eventual success value or failure reason. This lets asynchronous methods return values like synchronous methods: instead of the final value, the asynchronous method returns a promise of having a value at some point in the future.
[...]
A pending promise can become either fulfilled with a value, or rejected with a reason (error). When either of these happens, the associated handlers queued up by a promise's then method are called.

Categories