I have an issue where I may need to jump out of the whole promise chain because of some value or take two paths based on a value.
How best do you do that?
Here is the first scenario where I would like to just jump out of the whole chain. I just want to give them a message.
DB_WorkIssues.info().then(function (details) {
if (details.doc_count == 0 && details.update_seq == 0) {
showMsg("This device has no local data on it and no access to the Server, please come back when you are online.")
} jump out here, no need to do the next set.
else
return; Continue on as the values are valid.
}).then(function () {
return ajaxCallForJson(URI_LookupTables);
}).then(function (json) {
return callBulkDocLoad(DB_LookupTables, json);
}).then(function () {
return loadCategoriesDDL();
}).then(function () {
return loadEquipmentDDL();
}).catch(function (err) {
showMsg("Error in defineDBs: " + err);
});
In the 2nd scenario, I may want to take one path if the values are one thing and another if the values are another. But I still want the chains to work with the first promise. Something like this:
DB_WorkIssues.info().then(function (details) {
if (details.doc_count == 0 && details.update_seq == 0) {
Take this path.
return;
}).then(function () {
return ajaxCallForJson(URI_LookupTables);
}).then(function (json) {
return callBulkDocLoad(DB_LookupTables, json);
}).catch(function (err) {
showMsg("Error in defineDBs: " + err);
});
}
else
{
Take this path instead
return;
}).then(function () {
return loadCategoriesDDL();
}).then(function () {
return loadEquipmentDDL();
}).catch(function (err) {
showMsg("Error in defineDBs: " + err);
});
}
Thanks.
Here is what I was thinking after looking at the answer where I do the second promise always and only do the first in some cases.
DB_WorkIssues.info().then(function(details) {
// promise variable , defined in conditional
var promise;
Would I set the promise to some default value, in case the following test fails
if (details.doc_count == 0 && details.update_seq == 0) {
// return this promise
promise = ajaxCallForJson(URI_LookupTables).then(function(json) {
return callBulkDocLoad(DB_LookupTables, json);
});
}
return promise;
}).then(function () {
return loadCategoriesDDL();
}).then(function () {
return loadEquipmentDDL();
}).then(function () {
return loadLocationsDDL();
}).catch(function (err) {
showMsg("Error in defineDBs: " + err);
});
Is that how I could do it?
Thanks.
I think this is a skeleton that represents what you're going for. Promises are incredibly powerful and worth studying. I tried to add helpful comments but I suggest playing around with the code and understanding what's going on.
// Six named promise-creators. When called with (x), will create a promise
// which waits 200ms and then logs and resolves with (x).
// These could represent any asynchronous operation.
const p1 = p2 = p3 = p4 = p5 = p6 =
(x) => {
const p = new Promise((resolve, reject) => {
setTimeout(() => {resolve(x); console.log(x)}, 200)
});
return p;
}
// A function which, when called, will execute first promise chain.
const first_steps = () =>
p1(1)
.then(result => p2(2))
.then(result => p3(3))
// A function which, when called, will execute second promise chain.
const second_steps = () =>
p4(4)
.then(result => p5(5))
.then(result => p6(6))
// When true, this prints numbers 1-6.
// When false, only prints numbers 4-6.
if (false) {
console.log(first_steps().then(second_steps));
} else {
second_steps();
}
Seems to me you have extra sets of then() and your if() would determine which promise to return something like:
DB_WorkIssues.info().then(function(details) {
// promise variable , defined in conditional
var promise;
if (details.doc_count == 0 && details.update_seq == 0) {
// return this promise
promise = ajaxCallForJson(URI_LookupTables).then(function(json) {
return callBulkDocLoad(DB_LookupTables, json);
});
} else {
// or this promise
promise = loadCategoriesDDL().then(function() {
return loadEquipmentDDL();
});
}
promise.catch(function(err) {
showMsg("Error in defineDBs: " + err);
});
return promise;
})
Related
I have my function whose job is to go over a number of files (that use the values from the array as building blocks for file names) and download them using a reduce. It's more of a hack as of now but the Promise logic should work. Except it doesn.t
Here's my code:
function import_demo_files(data) {
/**
* Make a local copy of the passed data.
*/
let request_data = $.extend({}, data);
const get_number_of_files_1 = Promise.resolve({
'data' : {
'number_of_files' : 2
}
});
return new Promise((resolve, reject) => {
let import_files = get_number_of_files_1.then(function(response) {
new Array(response.data.number_of_files).fill(request_data.step_name).reduce((previous_promise, next_step_identifier) => {
let file_counter = 1;
return previous_promise.then((response) => {
if( response !== undefined ) {
if('finished_import' in response.data && response.data.finished_import === true || response.success === false) {
return import_files;
}
}
const recursively_install_step_file = () => import_demo_file({
demo_handle: request_data.demo_handle,
'step_name': request_data.step_name,
'file_counter': file_counter
}).call().then(function(response) {
file_counter++;
if('file_counter' in response.data && 'needs_resume' in response.data) {
if(response.data.needs_resume === true) {
file_counter = response.data.file_counter;
}
}
return response.data.keep_importing_more_files === true ? recursively_install_step_file() : response
});
return recursively_install_step_file();
}).catch(function(error) {
reject(error);
});
}, Promise.resolve())
}).catch(function(error) {
reject(error);
});
resolve(import_files);
});
}
Now, when I do:
const import_call = import_demo_files({ 'demo_handle' : 'demo-2', 'step_name' : 'post' });
console.log(import_call);
The console.log gives me back that import_call is, in fact a promise and it's resolved. I very much like the way return allows me to bail out of a promise-chain, but I have no idea how to properly resolve my promise chain in there, so clearly, it's marked as resolved when it isn't.
I would like to do import_call.then(... but that doesn't work as of now, it executes this code in here before it's actually done because of the improper handling in import_demo_files.
An asynchronous recursion inside a reduction isn't the simplest of things to cut your teeth on, and it's not immediately obvious why you would want to given that each iteration of the recursion is identical to every other iteration.
The reduce/recurse pattern is simpler to understand with the following pulled out, as outer members :
1. the `recursively_install_step_file()` function
1. the `new Array(...).fill(...)`, as `starterArray`
1. the object passed repeatedly to `import_demo_file()`, as `importOptions`)
This approach obviates the need for the variable file_counter, since importOptions.file_counter can be updated directly.
function import_demo_files(data) {
// outer members
let request_data = $.extend({}, data);
const importOptions = {
'demo_handle': request_data.demo_handle,
'step_name': request_data.step_name,
'file_counter': 1
};
const starterArray = new Array(2).fill(request_data.step_name);
function recursively_install_step_file() {
return import_demo_file(importOptions).then((res) => {
if('file_counter' in res.data && 'needs_resume' in res.data && res.data.needs_resume) {
importOptions.file_counter = res.data.file_counter; // should = be += ?
} else {
importOptions.file_counter++;
}
return res.data.keep_importing_more_files ? recursively_install_step_file() : res;
});
}
// the reduce/recurse pattern
return starterArray.reduce((previous_promise, next_step_identifier) => { // next_step_identifier is not used?
let importOptions.file_counter = 1; // reset to 1 at each stage of the reduction?
return previous_promise.then(response => {
if(response && ('finished_import' in response.data && response.data.finished_import || !response.success)) {
return response;
} else {
return recursively_install_step_file(); // execution will drop through to here on first iteration of the reduction
}
});
}, Promise.resolve());
}
May not be 100% correct but the overall pattern should be about right. Be prepared to work on it some.
I'm doing a recursive request with async/await whenever the received response has length === 0. The problem is that when some request returns the desired data, the resolve(data); part of the promise doesn't seem to work.
So, in my code, I have reached the point where I get to make multiple recursive calls and, finally, receive a response whose length is not 0.
Note: there are plenty of API keys published in Github if you want to test the code.
var apiKey = "yourApiKey";
var url = "https://api.nasa.gov/mars-photos/api/v1/rovers/curiosity/photos?sol=";
function requestData(url) {
return fetch(url).then(response => {
if(response.ok) {
return response.json().then(data => {
return Promise.resolve(data);
});
} else {
return Promise.reject(response.status);
}
});
}
function NasaRequest(sun, limit, frecuency) {
return new Promise(async (resolve, reject) => {
var data = await requestData(url + sun + "&api_key=" + apiKey);
if(data.photos.length === 0 && !limit) {
setTimeout(async () => {
console.log("Delay for next request (sun " + sun + "): ", frecuency);
return await NasaRequest(sun - 1, limit, frecuency);
}, frecuency);
} else {
console.log("Resolve data:", data); // Code acutally reaches this point
resolve(data); // But this doesn't seem to work
}
});
};
async function init() {
try {
const currentValue = await NasaRequest(2175, false, 2000);
console.log("currentValue:", currentValue); // I want to reach this point, but is like the promise never returns
}catch(err){
console.error(err);
}
}
init();
In that moment, I want to return the data in the response to the calling init() function, for what I use resolve(data);. But it doesn't seem to work.
What am I doing wrong?
The problem is on setTimeout. When you calling setTimeout it returns right away and implicitly return undefined. The subsequence return doesn't matter at that point. If all you want to do, is to pause, and then proceed try something like this
async function requestData(url) {
var response = await fetch(url);
if (response.ok) {
return response.json()
} else {
throw new Error(response.status);
}
}
function sleep(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
async function nasaRequest(sun, limit, freq) {
var data = await requestData(url + sun + "&api_key=" + apiKey);
if (data.photos.length === 0 && !limit) {
await sleep(freq);
console.log("Delay for next request (sun " + sun + "): ", freq);
return await nasaRequest(sun - 1, limit, freq);
} else {
console.log("Resolve data:", data);
return data;
}
};
async function init() {
try {
const currentValue = await nasaRequest(2175, false, 2000);
console.log("currentValue:", currentValue);
} catch (err) {
console.error(err);
}
}
init();
I added a simple sleep function to handle the pause. I also modified requestData (removed then and Promise parts).
Note that using this recursive approach you may run into stack overflow. To avoid that problem you can simply use a loop and check against your limit variable.
I am still new to Promises and async coding in JavaScript. I am trying to create a function that returns a promise that iterate through an array of objects with a setTimeout. On each element, I will pass it to another function that returns a Promise. If the element doesn't satisfy a condition, I put it into another array and pass that new array into the function for a recursive call 10 more times until it satisfy the condition. Here is the code:
const promiseFunc = (item) => {
return new Promise((resolve, reject) => {
// Do something
if (some_kind_of_error) {
return reject(the_error);
} else {
return resolve({
itemName: item.name,
status: (item.isComplete === 'complete')
});
}
});
};
const func2 = (listOfItems, count) => {
return new Promise((resolve, reject) => {
if (count > 10) {
reject(new Error("Too many attempts."));
}
setTimeout(() => {
const newList = [];
listOfItems.forEach(item => {
promiseFunc(item)
.then(result => {
if(result.isCompleted !== true) {
newList.push(item);
}
});
});
if (newList.length === 0) {
return resolve(true);
} else {
console.log('Calling func2 again');
return func2(newList, count+1);
}
}, 1000);
});
};
The problem is that when I run the func2 function, I always get true even if it is suppose to recurse.
When I tried to log things out, I notice that the message Calling func2 again was not logged out in the terminal. This means that no matter what, the condition for checking newList will always be empty hence it is always resolving true and never going to the else statement.
Can someone please explain why this is the current behavior? How do I make it so that my func2 will wait for the execution of if (newList.length === 0) until my forEach loop is done?
I was experimenting with Promise in nodejs to better understand how it works. I have partially working code, but I am not sure if I am using it correctly. My intention is to pass error or correct result back to caller function. This seem to get complicated with all these callbacks. Here is what I have so far.
//This is my main CALLER function
function query_mcafee_mssql_type(database, node_name, type) {
return new Promise(function (fulfill, reject) {
switch (type) {
case "currentDefinitionDate":
computeCurrentDefinitionResult(database, node_name)
.then(function (result) {
console.log('query_mcafee_mssql_type' + result);
fulfill(result);
});
break;
}
});
}
function computeCurrentDefinitionResult(database, node_name) {
return new Promise(function (fulfill, reject) {
var leaf_sql_query = "SELECT * FROM "+ JSON.stringify(database) +".dbo.EPOLeafNode WHERE NodeName=" + "'" + node_name + "'";
query_mcafee_mssql(leaf_sql_query)
.then(function (LeafNode) {
if (LeafNode == undefined) {
fulfill(LeafNode);
} else {
return LeafNode;
}
})
.then(function (LeafNode) {
console.log('computeCurrentDefinitionResult' + LeafNode);
var product_properties_sql_query = "SELECT * FROM "+ JSON.stringify(database) +".dbo.EPOProductProperties WHERE ParentID=" + "'" + LeafNode.AutoID + "'" + "AND ProductCode LIKE 'VIRUSCAN%'";
return query_mcafee_mssql(product_properties_sql_query);
})
.then(function (ProductProperty) {
if (ProductProperty == undefined) {
fulfill(ProductProperty);
} else {
return ProductProperty;
}
})
.then(function (ProductProperty) {
fulfill(ProductProperty.DATDate);
});
});
}
function query_mcafee_mssql(sql_string) {
return new Promise(function (fulfill, reject) {
query_mssql(mcafee_config, sql_string)
.then(function (sql_response) {
fulfill(sql_response);
console.log('query_mcafee' + sql_response);
});
});
}
function query_mssql(config, sql_string){
return new Promise(function (fulfill, reject) {
var connection = new sql.Connection(config, function(err) {
// ... error checks
if (err) {
console.log('connection to mssql has failed');
//throw err;
fulfill();
} else {
// Query
var request = new sql.Request(connection);
request.query(sql_string, function(err, recordset) {
// ... error checks should go here :
if (err) {
console.log('requst query error');
fulfill();
} else {
// output query result to console:
//console.log(recordset);
fulfill(recordset);
}
});
}
});
});
}
My main caller function is query_mcafee_mssql_type(). I use Promise to allow the execution of the query. Once that is done, if its an error, I would like "undefined" to returned else the correct result to returned to the caller.
As per my understanding, fulfill and reject callbacks decide the fate of Promise.
The top most in call stack is function query_mssql(). My assumption was that once I call "fulfill" with result if success or return fulfill() empty if error.
The function above that is query_mcafee_mssql() which is oblivious to error or success and just passes on the result.
The function computeCurrentDefinitionResult() is where all the problem arises. I need to make two sql query one after the other. However if first query fails then I don't see any point in proceeding with next query that means
query_mcafee_mssql(leaf_sql_query)
.then(function (LeafNode) {
if (LeafNode == undefined) {
fulfill(LeafNode);
} else {
return LeafNode;
}
})
I do not want rest of the .then to be executed as it does not make sense if LeafNode is undefined. I want to return the LeafNode value back to its caller. However if I return fulfill(), the code flow seem to move to next .then. If I use, reject(), the caller query_mcafee_mssql_type() .then block is not getting called. The relevant block is show below.
computeCurrentDefinitionResult(database, node_name)
.then(function (result) {
console.log('query_mcafee_mssql_type' + result);
fulfill(result);
});
- How can I return the actual result from computeCurrentDefinitionResult() ??
- Does all the functions need to return "Promise" to achieve what I am doing?
- Why does the code patch not return from the function after "fulfill()" is called?
- Is there a need to use "return" in these function blocks?
Any help would be appreciated. Thanks.
From your code, I got a feeling that you have no idea about the correct usage of resolve and reject in Promise. Here is the right one.
function query_mssql(config, sql_string){
return new Promise(function (resolve, reject) {
var connection = new sql.Connection(config, function(err) {
// ... error checks
if (err) {
return reject(err);
}
// Query
var request = new sql.Request(connection);
request.query(sql_string, function(err, recordset) {
// ... error checks should go here :
if (err) {
return reject(er);
}
// output query result to console:
//console.log(recordset);
resolve(recordset);
});
});
});
}
//Then use it like this
query_mssql(config,sql_string)
.then(function(LeafNode){
//query success
console.log(LeafNode);
}).catch(function(er){
//query failed,
console.log(er);
});
I a promise in such fashion,
function getMode(){
var deferred = Promise.defer();
checkIf('A')
.then(function(bool){
if(bool){
deferred.resolve('A');
}else{
return checkIf('B');
}
}).then(function(bool){
if(bool){
deferred.resolve('B');
}else{
return checkIf('C');
}
}).then(function(bool){
if(bool){
deferred.resolve('C');
}else{
deferred.reject();
}
});
return deferred.promise;
}
checkIf returns a promise, and yes checkIf cannot be modified.
How do I break out of the chain at the first match? (any way other than explicitly throwing error?)
Any way other than explicitly throwing error?
You may need to throw something, but it does not have to be an error.
Most promise implementations have method catch accepting the first argument as error type (but not all, and not ES6 promise), it would be helpful under this situation:
function BreakSignal() { }
getPromise()
.then(function () {
throw new BreakSignal();
})
.then(function () {
// Something to skip.
})
.catch(BreakSignal, function () { })
.then(function () {
// Continue with other works.
});
I add the ability to break in the recent implementation of my own promise library. And if you were using ThenFail (as you would probably not), you can write something like this:
getPromise()
.then(function () {
Promise.break;
})
.then(function () {
// Something to skip.
})
.enclose()
.then(function () {
// Continue with other works.
});
You can use
return { then: function() {} };
.then(function(bool){
if(bool){
deferred.resolve('A');
return { then: function() {} }; // end/break the chain
}else{
return checkIf('B');
}
})
The return statement returns a "then-able", only that the then method does nothing.
When returned from a function in then(), the then() will try to get the result from the thenable.
The then-able's "then" takes a callback but that will never be called in this case. So the "then()" returns, and the callback for the rest of the chain does not happen.
I think you don't want a chain here. In a synchronous fashion, you'd have written
function getMode(){
if (checkIf('A')) {
return 'A';
} else {
if (checkIf('B')) {
return 'B';
} else {
if (checkIf('C')) {
return 'C';
} else {
throw new Error();
}
}
}
}
and this is how it should be translated to promises:
function getMode(){
checkIf('A').then(function(bool) {
if (bool)
return 'A';
return checkIf('B').then(function(bool) {
if (bool)
return 'B';
return checkIf('C').then(function(bool) {
if (bool)
return 'C';
throw new Error();
});
});
});
}
There is no if else-flattening in promises.
I would just use coroutines/spawns, this leads to much simpler code:
function* getMode(){
if(yield checkIf('A'))
return 'A';
if(yield checkIf('B'))
return 'B';
if(yield checkIf('C'))
return 'C';
throw undefined; // don't actually throw or reject with non `Error`s in production
}
If you don't have generators then there's always traceur or 6to5.
You could create a firstSucceeding function that would either return the value of the first succeeded operation or throw a NonSucceedingError.
I've used ES6 promises, but you can adapt the algorithm to support the promise interface of your choice.
function checkIf(val) {
console.log('checkIf called with', val);
return new Promise(function (resolve, reject) {
setTimeout(resolve.bind(null, [val, val === 'B']), 0);
});
}
var firstSucceeding = (function () {
return function (alternatives, succeeded) {
var failedPromise = Promise.reject(NoneSucceededError());
return (alternatives || []).reduce(function (promise, alternative) {
return promise.then(function (result) {
if (succeeded(result)) return result;
else return alternative();
}, alternative);
}, failedPromise).then(function (result) {
if (!succeeded(result)) throw NoneSucceededError();
return result;
});
}
function NoneSucceededError() {
var error = new Error('None succeeded');
error.name = 'NoneSucceededError';
return error;
}
})();
function getMode() {
return firstSucceeding([
checkIf.bind(null, 'A'),
checkIf.bind(null, 'B'),
checkIf.bind(null, 'C')
], function (result) {
return result[1] === true;
});
}
getMode().then(function (result) {
console.log('res', result);
}, function (err) { console.log('err', err); });
i like a lot of the answers posted so far that mitigate what the q readme calls the "pyramid of doom". for the sake of discussion, i'll add the pattern that i plunked out before searching around to see what other people are doing. i wrote a function like
var null_wrap = function (fn) {
return function () {
var i;
for (i = 0; i < arguments.length; i += 1) {
if (arguments[i] === null) {
return null;
}
}
return fn.apply(null, arguments);
};
};
and i did something totally analogous to #vilicvane's answer, except rather than throw new BreakSignal(), i'd written return null, and wrapped all subsequent .then callbacks in null_wrap like
then(null_wrap(function (res) { /* do things */ }))
i think this is a good answer b/c it avoids lots of indentation and b/c the OP specifically asked for a solution that doesn't throw. that said, i may go back and use something more like what #vilicvane did b/c some library's promises might return null to indicate something other than "break the chain", and that could be confusing.
this is more a call for more comments/answers than a "this is definitely the way to do it" answer.
Probably coming late the party here, but I recently posted an answer using generators and the co library that would answer this question (see solution 2):
https://stackoverflow.com/a/43166487/1337392
The code would be something like:
const requestHandler = function*() {
const survey = yield Survey.findOne({
_id: "bananasId"
});
if (survey !== null) {
console.log("use HTTP PUT instead!");
return;
}
try {
//saving empty object for demonstration purposes
yield(new Survey({}).save());
console.log("Saved Successfully !");
return;
}
catch (error) {
console.log(`Failed to save with error: ${error}`);
return;
}
};
co(requestHandler)
.then(() => {
console.log("finished!");
})
.catch(console.log);
You would pretty much write synchronous code that would be in reality asynchronous !
Hope it helps!
Try to use libs like thisone:
https://www.npmjs.com/package/promise-chain-break
db.getData()
.then(pb((data) => {
if (!data.someCheck()) {
tellSomeone();
// All other '.then' calls will be skiped
return pb.BREAK;
}
}))
.then(pb(() => {
}))
.then(pb(() => {
}))
.catch((error) => {
console.error(error);
});