I have a service designed which takes some parameter and then loops through input array, and find out which item is empty and then add those to and array and then after $q.all resolved it should give me the array of empty items.
input is array of items
function getElements(inputs) {
var elements= [],
promise, whenPromise,
promises = [],
mainPromise = $q.defer();
if (inputs.length === 0) {
mainPromise.resolve(elements);
return mainPromise.promise;
}
angular.forEach(inputs, function (input) {
promise = getPromises(input);
whenPromise = $q.resolve(promise).then(function (response) {
$timeout(function() {
if (response.isEmpty) {
**//perform action with the response.data;**
}
});
}, function () {
});
promises.push(whenPromise);
});
$q.all(promises).finally(function () {
mainPromise.resolve(outdatedEntities);
});
return mainPromise.promise;
}
function getPromises(input) {
var deferred = $q.defer();
someSerivce.getItemDetails(input.Id).then(function (value) {
if (value === null) {
$timeout(function () {
deferred.resolve({data: input, isEmpty: true});
});
} else {
//have item locally, but need to see if it's current
input.isEmpty().then(function (isEmpty) {
$timeout(function () {
deferred.resolve({data: input, isEmpty: isEmpty});
});
}, function (error) {
deferred.reject(error);
});
}
});
return deferred.promise;
}
Now, the problem is the $q.all is never resolved. Even though all the internal promises are getting resolved.
This should work:
function getElements(inputs) {
var elements = [],
promise, whenPromise,
promises = [],
mainPromise = $q.defer();
if (inputs.length === 0) {
mainPromise.resolve(elements);
return mainPromise.promise;
}
angular.forEach(inputs, function (input) {
promise = getPromises(input);
whenPromise = promise.then(function (response) {
if (response.isEmpty) {
* *//perform action with the response.data;**
}
}, function () {
});
promises.push(whenPromise);
});
return $q.all(promises).finally(function () {
return outdatedEntities;
});
}
function getPromises(input) {
return someSerivce.getItemDetails(input.Id).then(function (value) {
if (value === null) {
return {data: input, isEmpty: true};
} else {
//have item locally, but need to see if it's current
return input.isEmpty().then(function (isEmpty) {
return {data: input, isEmpty: isEmpty};
}, function (error) {
return error;
});
}
});
}
Related
I'm learning JavaScript, and I decided that an excelent chalenge would be to implement a custom Promise class in JavaScript. I managed to implement the method then, and it works just fine, but I'm having difficulties with the error handling and the method catch. Here is my code for the Promise class (in a module called Promise.mjs):
export default class _Promise {
constructor(executor) {
if (executor && executor instanceof Function) {
try {
executor(this.resolve.bind(this), this.reject.bind(this));
} catch (error) {
this.reject(error);
}
}
}
resolve() {
if (this.callback && this.callback instanceof Function) {
return this.callback(...arguments);
}
}
reject(error) {
if (this.errorCallback && this.errorCallback instanceof Function) {
return this.errorCallback(error);
} else {
throw `Unhandled Promise Rejection\n\tError: ${error}`;
}
}
then(callback) {
this.callback = callback;
return this;
}
catch(errorCallback) {
this.errorCallback = errorCallback;
return this;
}
}
When I import and use this class in the following code, all the then() clauses run as according, and I get the desired result in the console:
import _Promise from "./Promise.mjs";
function sum(...args) {
let total = 0;
return new _Promise(function (resolve, reject) {
setTimeout(function () {
for (const arg of args) {
if (typeof arg !== 'number') {
reject(`Invalid argument: ${arg}`);
}
total += arg;
}
resolve(total);
}, 500);
});
}
console.time('codeExecution');
sum(1, 3, 5).then(function (a) {
console.log(a);
return sum(2, 4).then(function (b) {
console.log(b);
return sum(a, b).then(function (result) {
console.log(result);
console.timeEnd('codeExecution');
});
});
}).catch(function (error) {
console.log(error);
});
But, when I add an invalid argument to the sum() function, i.e. not a number, the reject() method runs, but it don't stop the then() chain, as should be, and we also get an exception. This can be seen from the following code:
import _Promise from "./Promise.mjs";
function sum(...args) {
let total = 0;
return new _Promise(function (resolve, reject) {
setTimeout(function () {
for (const arg of args) {
if (typeof arg !== 'number') {
reject(`Invalid argument: ${arg}`);
}
total += arg;
}
resolve(total);
}, 500);
});
}
console.time('codeExecution');
sum(1, 3, '5').then(function (a) {
console.log(a);
return sum(2, 4).then(function (b) {
console.log(b);
return sum(a, b).then(function (result) {
console.log(result);
console.timeEnd('codeExecution');
});
});
}).catch(function (error) {
console.log(error);
});
Also, if I catch an error in nested then() methods, the outer catch() doesn't notice this and I get an exception again. The goal is to implement a lightweight functional version of Promises, but not necessarily with all its functionality. Could you help me?
The problem in your code is that your sum function calls both the reject and the resolve functions. There's no handling in the sum function that will cause it not to call the resolve function at the end, and there is nothing in your _Promise that blocks this behavior.
You have 2 options to fix this.
Option 1 would be if you want your _Promise to act like a real Promise you will need to manage a state and once a promise got to a final state stop calling the callback or errorCallback.
Option 2 would be to prevent from calling both reject and resolve in the function calling the _Promise, in this case, the sum function.
With the comments that you guys provide me, I was able to improve the code and correct the errors mentioned, as shown below. Now, I would like you to give me suggestions on how to proceed and improve the code. Thanks. (The code can also be found on github).
const PENDING = 0;
const FULFILLED = 1;
const REJECTED = 2;
function _Promise(executor) {
let state = PENDING;
let callOnFulfilled = [];
let callOnRejected = undefined;;
function resolve(...args) {
if (!state) {
state = FULFILLED;
}
resolveCallbacks(...args);
};
function reject(error) {
state = REJECTED;
if (callOnRejected && (callOnRejected instanceof Function)) {
callOnRejected(error);
callOnRejected = undefined;
callOnFulfilled = [];
} else {
throw `Unhandled Promise Rejection\n\tError: ${error}`;
}
};
function resolveCallbacks(...value) {
if (state !== REJECTED) {
let callback = undefined;
do {
callback = callOnFulfilled.shift();
if (callback && (callback instanceof Function)) {
const result = callback(...value);
if (result instanceof _Promise) {
result.then(resolveCallbacks, reject);
return;
} else {
value = [result];
}
}
} while (callback);
}
};
if (executor && (executor instanceof Function)) {
executor(resolve, reject);
}
this.then = function (onFulfilled, onRejected) {
if (onFulfilled) {
callOnFulfilled.push(onFulfilled);
if (state === FULFILLED) {
resolveCallbacks();
}
}
if (onRejected && !callOnRejected) {
callOnRejected = onRejected;
}
return this;
};
this.catch = function (onRejected) {
return this.then(undefined, onRejected);
};
}
function sum(...args) {
let total = 0;
return new _Promise(function (resolve, reject) {
setTimeout(function () {
for (const arg of args) {
if (typeof arg !== 'number') {
reject(`Invalid argument: ${arg}`);
}
total += arg;
}
resolve(total);
}, 500);
});
}
console.time('codeExecution');
sum(1, 3, 5).then(function (a) {
console.log(a);
return sum(2, 4).then(function (b) {
console.log(b);
return sum(a, b).then(function (result) {
console.log(result);
return 25;
});
}).then(function (value) {
console.log(value);
console.timeEnd('codeExecution');
});
}).catch(function (error) {
console.log(error);
});
I have a factory object that contain private object, which is used to cache result retrieved from the api using the factory available functions.
global.mainApp.factory('SessionFactory', function (UserEndpointsResource, SiteEndpointsResource) {
var data = {
/**
* #type {boolean|null}
*/
isLoggedIn: null
};
return {
isUserLoggedIn: function (callback) {
if (data.isLoggedIn != null) {
callback(data.isLoggedIn);
}
else {
UserEndpointsResource.isLoggedIn().$promise.then(function (res) {
var isUserLoggedIn = res.status == 1;
// Trying to set the result to the outer scope data variable
data.isLoggedIn = isUserLoggedIn;
callback(isUserLoggedIn);
}, function (failureData) {
data.isLoggedIn = false;
callback(false);
});
}
},
...
};
});
The problem that every time I call the isUserLoggedIn function, data.isLoggedIn is always null.
How can I alter the factory data object inside the promise then function?
Thanks.
Using the suggestion supplied in the comments, aka do not store promise results, store promises themselves, this is the modified working code!
global.mainApp.factory('SessionFactory', function (UserEndpointsResource, SiteEndpointsResource) {
var data = {
/**
* #type {boolean|null}
*/
isLoggedIn: null
};
return {
isUserLoggedIn: function (callback) {
if (data.isLoggedIn != null) {
data.isLoggedIn.then(function (isLoggedIn) {
callback(isLoggedIn.status == 1);
});
}
else {
data.isLoggedIn = UserEndpointsResource.isLoggedIn().$promise.then(function (res) {
var isUserLoggedIn = res.status == 1;
callback(isUserLoggedIn);
return isUserLoggedIn;
}, function (failureData) {
data.isLoggedIn = false;
callback(false);
return null;
});
}
}
};
});
I have a function that needs to return a list of favorite locations. Something like this
LocationsFactory.getFavoriteLocations().then(function($favoriteLocations) {
});
The getFavoriteLocations looks something like this
getFavoriteLocations: function() {
if (favorite_locations.length == 0)
{
var deff = $q.defer();
obj.getDeviceId().then(function(device_id) {
$http.get('url?token=' + device_id).then(function(response) {
favorite_locations = response.data;
deff.resolve(favorite_locations);
return deff.promise;
})
})
} else {
return favorite_locations;
}
}
The getDeviceId again, it's a function based on promise.
getDeviceId: function() {
var deff = $q.defer();
deff.resolve(Keychain.getKey());
return deff.promise;
}
The error that I got is TypeError: Cannot read property 'then' of undefined. Please help!
You can chain promises:
getFavoriteLocations: function () {
if (favorite_locations.length === 0) {
return obj.getDeviceId().then(function (device_id) {
return $http.get('url?token=' + device_id).then(function (response) {
favorite_locations = response.data;
return favorite_locations;
});
});
}
return $q.resolve(favorite_locations);
}
And improve this:
getDeviceId: function() {
return $q.resolve(Keychain.getKey());
}
$q in not necessary here:
if (favorite_locations.length == 0)
{
return obj.getDeviceId() // you have to return a promise here
.then(function(device_id) {
return $http.get('url?token=' + device_id) // you will access the response below
})
.then(function(response) {
favorite_locations = response.data;
return favorite_locations
});
})
}
Now it should work.
I am trying to write this code with Promise. but I don't know how to write promise inside Promise and loop.
I tried to think like this but insertBook function become asynchronously.
How can I get bookId synchronously?
update: function(items, quotationId) {
return new Promise(function(resolve, reject) {
knex.transaction(function (t) {
Promise.bind(result).then(function() {
return process1
}).then(function() {
return process2
}).then(function() {
var promises = items.map(function (item) {
var people = _.pick(item, 'familyName', 'firstNumber', 'tel');
if (item.type === 'book') {
var book = _.pick(item, 'name', 'bookNumber', 'author');
var bookId = insertBook(t, book);
var values = _.merge({}, people, {quotation: quotationId}, {book: bookId});
} else {
var values = _.merge({}, people, {quotation: quotationId});
}
return AModel.validateFor(values);
});
return Promise.all(promises);
}).then(function(items) {
var insertValues = items.map(function (item) {
return People.columnize(item);
});
return knex('people').transacting(t).insert(insertValues);
}).then(function() {
return process5
}).then(function() {
...........
}).then(function() {
t.commit(this);
}).catch(t.rollback);
}).then(function (res) {
resolve(res);
}).catch(function(err) {
reject(err);
});
});
}
function insertBook(t, book){
return Promise.bind(this).then(function () {
return Book.columnizeFor(book);
}).then(function (value) {
return knex('book').transacting(t).insert(value, "id");
});
}
You dont need to get bookid synchronously, you can handle it asynchronously correctly. Also, it is possible you want all book insertions happen sequentially, so I refactored the Promise.all part. (done that just to give you an idea. Promise.all should work fine if insertions in parallel are allowed). Furthermore, I think you shouldn't use Promise.bind. To be honest I dont even know what it does, one thing for sure: it doesn't work with standard promises. So here is an example how I think it should work:
update: function(items) {
return new Promise(function(resolve) {
knex.transaction(function (t) {
resolve(Promise.resolve().then(function() {
return process1;
}).then(function() {
return process2;
}).then(function() {
var q = Promise.resolve(), results = [];
items.forEach(function (item) {
q = q.then(function() {
var book = _.pick(item, 'name', 'bookNumber', 'author');
return insertBook(t, book);
}).then(function(bookId) {
var people = _.pick(item, 'familyName', 'firstNumber', 'tel');
var values = _.merge({}, people, {book: bookId});
return AModel.validateFor(values);
}).then(function(item) {
results.push(item);
});
});
return q.then(function() {
return results;
});
}).then(function(items) {
return process4
}).then(function() {
t.commit(result);
}).catch(function(e) {
t.rollback(e);
throw e;
}));
});
});
}
function insertBook(t, book){
return Promise.resolve().then(function () {
return Book.columnizeFor(book);
}).then(function (value) {
return knex('book').transacting(t).insert(value, "id");
});
}
Assuming that insertBook returns a promise you could do
var people = _.pick(item, 'familyName', 'firstNumber', 'tel');
if (item.type === 'book') {
var book = _.pick(item, 'name', 'bookNumber', 'author');
return insertBook(t, book)
.then(bookId => _.merge({}, people, {quotation: quotationId}, {book: bookId}))
.then(AModel.validateFor)
} else {
return Promise.resolve(_.merge({}, people, {quotation: quotationId}))
.then(AModel.validateFor)
}
I'm in a scenario where I have to get data from the server in parts in sequence, and I would like to do that with the help of Promises. This is what I've tried so far:
function getDataFromServer() {
return new Promise(function(resolve, reject) {
var result = [];
(function fetchData(nextPageToken) {
server.getData(nextPageToken).then(function(response) {
result.push(response.data);
if (response.nextPageToken) {
fetchData(response.nextPageToken);
} else {
resolve(result);
}
});
})(null);
});
}
getDataFromServer().then(function(result) {
console.log(result);
});
The first fetch is successful, but subsequent calls to server.getData() does not run. I presume that it has to do with that the first then() is not fulfilled. How should I mitigate this problem?
Nimrand answers your question (missing catch), but here is your code without the promise constructor antipattern:
function getDataFromServer() {
var result = [];
function fetchData(nextPageToken) {
return server.getData(nextPageToken).then(function(response) {
result.push(response.data);
if (response.nextPageToken) {
return fetchData(response.nextPageToken);
} else {
return result;
}
});
}
return fetchData(null);
}
getDataFromServer().then(function(result) {
console.log(result);
})
.catch(function(e) {
console.error(e);
});
As you can see, recursion works great with promises.
var console = { log: function(msg) { div.innerHTML += "<p>"+ msg +"</p>"; }};
var responses = [
{ data: 1001, nextPageToken: 1 },
{ data: 1002, nextPageToken: 2 },
{ data: 1003, nextPageToken: 3 },
{ data: 1004, nextPageToken: 4 },
{ data: 1005, nextPageToken: 0 },
];
var server = {
getData: function(token) {
return new Promise(function(resolve) { resolve(responses[token]); });
}
};
function getDataFromServer() {
var result = [];
function fetchData(nextPageToken) {
return server.getData(nextPageToken).then(function(response) {
result.push(response.data);
if (response.nextPageToken) {
return fetchData(response.nextPageToken);
} else {
return result;
}
});
}
return fetchData(0);
}
getDataFromServer().then(function(result) {
console.log(result);
})
.catch(function(e) { console.log(e); });
<div id="div"></div>
Because your then statement doesn't pass a function to handle error cases, requests to the server for data can fail silently, in which case the promise returned by getDataFromServer will never complete.
To fix this, pass a second function as an argument to then, as below:
function getDataFromServer() {
return new Promise(function(resolve, reject) {
var result = [];
(function fetchData(nextPageToken) {
server.getData(nextPageToken).then(function(response) {
result.push(response.data);
if (response.nextPageToken) {
fetchData(response.nextPageToken);
} else {
resolve(result);
}
}).catch(function(error) {
//Note: Calling console.log here just to make it easy to confirm this
//was the problem. You may wish to remove later.
console.log("Error occurred while retrieving data from server: " + error);
reject(error);
});
})(null);
});
}