Follow on actions after asynch function completion - javascript

I'm going to do my best to explain this one since this is a reduced version of the real code and some parts might seem redundant or excessive (and they might be). The model can be found at https://jsfiddle.net/ux5t7ykm/1/. I am using a function to build an array of values that are passed to another function to do a REST call for the data that corresponds to the values. The array lists the name of the list to get the data from, the field name, the filter field name, the filter value, and the target array to pass the autocomplete values back to. This part is working fine and is essentially what Step1() does. setAutocomplete() then uses getData() to make the multiple REST calls. I'm using sprLib for that part so it's already a promise (this is getListItems() normally). getData() is where the magic is happening. It recursively cycles through the list and performs getListItems() until all the data is retrieved. I have this set as an async function with await. This also seems to be running fine.
The problem I have is that once this whole sequence is complete, I then have another function that I need to run. It manipulates the data from the previous function. I've tried to put step1() in a promise, make it async, etc. Nothing seems to work. CheatStep() seems to be where a response or some other method signaling to Step1() should go to then allow the next function to run. I've been banging my head against this all week and I feel like my understanding of promises and asnyc is regressing.
var dict = [{
'id': '1',
'title': 'test1'
}, {
'id': '2',
'title': 'test2'
}, {
'id': '3',
'title': 'test3'
}, {
'id': '4',
'title': 'test4'
}, ]
step1()
nextStep()
function nextStep() {
console.log('I run after Step1')
}
function cheatStep() {
console.log('I cheated')
}
function step1() {
setAutoComplete(4);
}
function setAutoComplete(listIndex) {
getData(listIndex,
function(result) {
for (var i = 0; i < listIndex; i++) {
var autoDict = result[i];
console.log(autoDict)
}
cheatStep()
},
function(e) {
console.error(e);
alert('An error occurred wile setting autocomplete data for item');
})
}
async function getData(listIndex, success, error, curIndex, result) {
var curIndex = curIndex || 0;
var result = result || {};
await getListItems(curIndex,
function(listItems) {
result[curIndex] = listItems;
curIndex++;
if (listIndex > curIndex) {
getData(listIndex, success, error, curIndex, result);
console.log('1');
} else {
console.log('2');
success(result);
}
},
error)
}
function getListItems(curIndex, success, error) {
new Promise(
function(resolve, reject) {
var x = dict[curIndex];
resolve(x);
})
.then(function(x) {
success(x);
});
}

I have adopted your code to a more async/await view coz you still use heavily callback approach:
var dict = [{
'id': '1',
'title': 'test1'
}, {
'id': '2',
'title': 'test2'
}, {
'id': '3',
'title': 'test3'
}, {
'id': '4',
'title': 'test4'
}, ]
function nextStep(result) {
console.log('I run after Step1',result)
}
function cheatStep() {
console.log('I cheated')
}
step1().then((result)=>{nextStep(result)})
async function step1() {
return await setAutoComplete(4);
}
async function setAutoComplete(listIndex) {
const result = await getData(listIndex);
for (var i = 0; i < listIndex; i++) {
var autoDict = result[i];
console.log(autoDict)
}
cheatStep()
return result
}
async function getData(listIndex, curIndex=0, result={}) {
const listItems = await getListItems(curIndex)
result[curIndex] = listItems;
curIndex++;
if (listIndex > curIndex) {
return await getData(listIndex, curIndex, result);
} else {
console.log('2');
return (result);
}
}
function getListItems(curIndex) {
return new Promise(
(resolve, reject) =>{
resolve(dict[curIndex]);
})
}
I also kept recursion here, but personally I would prefer solution with a loop instead.
Also a minor advise. Try to avoid write declaration after usage:
nextStep()
function nextStep() {
console.log('I run after Step1')
}
JS hoisting does that a valid syntax but it is really hard to read=)

Related

Javascript call function according to user input

I want to call a unique function according to the user's input on JavaScript. I'm working with Node.js, not HTML inputs. I'll leave an example code that explains my general code's goal. Is there any better approach than I did? What would you recommend to me? I don't want to add all the new functions to Map. I want to turn into dynamic too.
let functions = new Map([
["Annually", annually],
["Daily", daily],
["Weekly", weekly],
]);
async function choice() {
const answer = await inquirer.prompt({
name: "choice",
type: "list",
message: "How often do you want to get a notification?",
choices: ["Annually", "Daily", "Weekly"], //Gets choice from users think it's like a HTML form
});
functions.get(answer.choice)(); // Gets the function from map after that calls the function
}
async function annually() {
console.log("Annually works.");
}
async function weekly() {
console.log("Weekly works.");
}
async function daily() {
console.log("Daily works.");
}
i think this solution is better
const funcs = {
annually: async () => { // you can change "annually" to anything
console.log('Annually works.')
},
weekly: async () => {
console.log('Weekly works.')
},
daily: async () => {
console.log('Daily works.')
},
}
async function choice() {
const answer = await inquirer.prompt({
name: 'choice',
type: 'list',
message: 'How often do you want to get a notification?',
choices: Object.keys(funcs),
})
await funcs[answer.choice]()
}
The way you are accomplishing this seems like it would work, but I think there is a better way to do this that is more readable.
function handleDaily(){
console.log("daily works");
}
function handleWeekly(){
console.log("weekly works");
}
function handleAnnually(){
console.log("annually works");
}
var CHOICE_LIST = [];
const CHOICES = [
{value: "Daily", handle: handleDaily},
{value: "Weekly", handle: handleWeekly},
{value: "Annually", handle: handleAnnually},
];
//Builds choice list based off of CHOICES array
for(let i = 0; i < CHOICES.length; i++){
CHOICE_LIST.push(CHOICES[i].value);
}
function evalInput(input) {
for(let i = 0; i < CHOICES.length; i++){
if(input === CHOICES[i].value){
CHOICES[i].handle();
break;
}
}
}
async function getChoice(){
const answer = await inquirer.prompt({
name: "choice",
type: "list",
message: "How often do you want to get a notification?",
choices: CHOICE_LIST
});
evalInput(answer.choice);
}
No need to store the functions in a map unless you really need to. I also removed the async keyword from your functions that were not awaiting anything.
If these functions are in global object's scope you can do this:
var fn = answer.choice.toString();
if (global[fn] === "function") {
global[fn]();
}
If its in a module and you know the module exports
var someExports = require('./someExportsFile.js');
Object.keys(someExports)[fn];
PS:
If this was in browser, you could have used
var brFn = window[fn)];
if (typeof brFn === "function") brFn();

Async and Await function not working properly

I seem to be running into an issue with my async and await functions not working and I am relatively new to this...
When I call the CallToPopulateCustomerDropDown, I never get to the call for SetCustomerMultiColumnAfterAddingNewCustomerFromNewOrder ( I know this because I have break point here in it and it never gets hit). So I am figuring that I have written something wrong
Here is my code
async function CallToPopulateCustomerDropDown(ddlCustomer, selectedValue) {
await CallPopulateCustomerDropDown(ddlCustomer, selectedValue);
SetCustomerMultiColumnAfterAddingNewCustomerFromNewOrder();
}
function CallPopulateCustomerDropDown(ddlCustomer, selectedValue) {
try {
return new Promise((resolve, reject) => {
LogCallStackToConsole('populateCustomerDropDown(ddlCustomer, selectedValue)');
//#region Variable Declarations
let orderField, grid, ds, foundCatalog, dd;
//#endregion
for (i = 0; i < CustomersList.length; i++) {
if (CustomersList[i].Company.length == 0) {
CustomersList[i].Company = CustomersList[i].FirstName + " " + CustomersList[i].LastName
}
}
$(ddlCustomer).empty();
$(ddlCustomer).kendoMultiColumnComboBox({
placeholder: "Select Customer...",
dataTextField: "Company",
dataValueField: "CustomerID",
height: 300,
columns: [
{ field: "FirstName", title: "First", width: 200 },
{ field: "LastName", title: "Last", width: 200 },
{ field: "Company", title: "Company", width: 200 }
],
footerTemplate: "#: instance.dataSource.total() # Customers Found",
filter: "contains",
filterFields: ["FirstName", "LastName", "Company"],
dataSource: {
data: CustomersList,
sort: [
{ field: "FirstName", dir: "asc" },
{ field: "LastName", dir: "asc" },
{ field: "Company", dir: "asc" }
]
},
change: function () {
},
select: function (e) {
LogCallStackToConsole('populateCustomerDropDown(ddlCustomer, selectedValue).select');
orderField = $('#txtOrderName').val();
grid = $('#gridNewOrder').getKendoGrid();
ds = grid.dataSource.view();
foundCatalog;
for (let i = 0; i < ds.length; i++) {
if (ds[i].Catalog.length > 0) {
foundCatalog = true;
break;
}
}
if (orderField.length > 0 && foundCatalog) {
$('#btnOK').prop("disabled", false);
}
}
});
if (selectedValue != null) {
dd = $(ddlCustomer).data("kendoMultiColumnComboBox");
dd.value(selectedValue);
}
});
resolve();
}
catch (err) {
reject();
}
}
function SetCustomerMultiColumnAfterAddingNewCustomerFromNewOrder() {
let customerMultiColumn = $('#ddlCustomer').data("kendoMultiColumnComboBox");
customerMultiColumn.select(function (dataItem) {
return dataItem.Company === g_CustomerEditorObject.companyName;
});
ResetGlobalObject(g_CustomerEditorObject);
}
Shortening your code, you're doing this:
return new Promise((resolve, reject) => {
...
});
resolve();
Note how you're calling resolve() outside of the Promise callback function block, meaning your code doesn't have access to it. You're also doing the same with catch(), you're using it outside of the function it's available in. If you want to learn more about this, you can research "Javascript scope."
You're probably trying to do this:
return new Promise((resolve, reject) => {
try {
LogCallStackToConsole('populateCustomerDropDown(ddlCustomer, selectedValue)');
...
resolve();
}
catch (err) {
reject();
}
});
However, your code isn't actually doing anything asynchronous, so you don't need a promise here, at least not in this implementation. A promise is meant to handle a function that finishes "later", like a network call. Your function appears to execute synchronously like a regular Javascript function. There's no callback or operation you're waiting for.
You're calling resolve outside of the promise
Also, it's sort of bad design to try catch a promise if the function is returning the promise. The code calling the function should try/catch the returned promise, like so:
async function CallToPopulateCustomerDropDown(ddlCustomer, selectedValue) {
try{
await CallPopulateCustomerDropDown(ddlCustomer, selectedValue);
SetCustomerMultiColumnAfterAddingNewCustomerFromNewOrder();
}catch(err){
console.log(err)
}
}
function CallPopulateCustomerDropDown(ddlCustomer, selectedValue) {
return new Promise((resolve, reject) => {
LogCallStackToConsole('populateCustomerDropDown(ddlCustomer, selectedValue)');
// ....the rest of your code
// if you code has a problem, you can choose to reject
// return reject('the reason')
resolve()
});
}

Sequelize nodejs pass value to another function

Here is my function which return count fine.But I want its value to be used in my response.
var PostComment = require("../models/PostComment");
var getPostCount = function (postId,res) {
return PostComment.findAll({where: {post_id: postId}}).then(function (comments) {
return comments.length;
});
}
module.exports = {
getPostCount: getPostCount
}
Here is my another function. Console log working fine. But I need to return value.
arr.push({
id: item.id,
post_userid: item.post_userid,
post_sendername: item.post_sendername,
post_description: item.post_description,
attach_status: item.attach_status,
post_datetime: item.post_datetime,
post_status: item.post_status,
remove_id: item.remove_id,
attachment_list: item.tbl_post_attachments,
total_like:totalLikes,
comment_count:Samparq.getPostCount(item.id, res).then(function (commentCount) {
console.log(commentCount);
}),
comment_list:item.tbl_post_comments
});
SIDENOTE:
You can use the count method in your first code block:
var PostComment = require("../models/PostComment");
var getPostCount = function (postId) {
return PostComment.count({where: {post_id: postId}});
}
module.exports = {
getPostCount: getPostCount
}
In your second code block, you're getting a Promise back in comment_count, not an actual number.
You need to first get the count, then add it to the object.
Samparq.getPostCount(item.id).then(function (count) {
arr.push({
id: item.id,
post_userid: item.post_userid,
post_sendername: item.post_sendername,
post_description: item.post_description,
attach_status: item.attach_status,
post_datetime: item.post_datetime,
post_status: item.post_status,
remove_id: item.remove_id,
attachment_list: item.tbl_post_attachments,
total_like:totalLikes,
comment_count: count,
comment_list:item.tbl_post_comments
});
})
SIDENOTE #2:
If you use async await, which is new to node, you'll be able to make it look more like your original code. If you're inside an async function, then your second codeblock could look like this:
arr.push({
id: item.id,
post_userid: item.post_userid,
post_sendername: item.post_sendername,
post_description: item.post_description,
attach_status: item.attach_status,
post_datetime: item.post_datetime,
post_status: item.post_status,
remove_id: item.remove_id,
attachment_list: item.tbl_post_attachments,
total_like:totalLikes,
comment_count: await Samparq.getPostCount(item.id),
comment_list:item.tbl_post_comments
});

Return promises in order

I'm facing an issue to return promises using $q#all method.
I want to make promises dependent on each other, i.e.:
If I set obj1, obj2 and obj3 I want to get them in the same order.
How can I achieve this?
Factory:
mainFactory.$inject = ['$http', '$q'];
function mainFactory($http, $q) {
var mainFactory = {
getPromises: getPromises
};
return mainFactory;
function getPromises(id) {
promises = {
'obj1': $http.get('http1'),
'obj2': $http.get('http2'),
'obj3': $http.get('http3'),
'obj4': $http.get('http4', { params: { 'id': id } }),
'obj5': $http.get('http5'),
'obj6': $http.get('http6', { params: { 'id': id } })
};
return $q.all(promises);
}
}
Controller:
MainCtrl.$inject = ['mainFactory'];
function MainCtrl(mainFactory) {
var vm = this;
mainFactory.getPromises(id)
.then(getResponse)
.catch(getError);
function getResponse(response) {
var keys = Object.keys(response), i = keys.length;
while (i--) {
var key = keys[i];
console.log(key); // I want all the keys in order, i.e. => obj1, obj2.. and so on
var value = response[key].data;
switch(key) {
...
}
}
}
function getError(error) {
console.log(error);
}
}
EDIT:
I tried this way also:
var promises = {};
return $http.get('/admin/http1.json').then(function (value) {
promises['obj1'] = value;
})
.then(function (result) {
return $http.get('/admin/http2.json').then(function (value) {
promises['obj2'] = value;
});
}).then(function (result) {
return $http.get('/admin/http3.json').then(function (value) {
promises['obj3'] = value;
});
});
return $q.all(promises);
Using $q.all will resolve each promise in no particular order. If you want them to execute after each promise has been resolve, use promise chaining.
function getPromises(id) {
var getObjA = function () {
return $http.get('http1');
};
var getObjB = function () {
return $http.get('http2');
};
var getObjC = function () {
return $http.get('http3');
};
var getObjD = function () {
return $http.get('http4', { params: { 'id': id } });
};
var getObjE = function () {
return $http.get('http5');
};
var getObjF = function () {
return $http.get('http5', { params: { 'id': id } });
};
return getObjA()
.then(getObjB)
.then(getObjC)
.then(getObjD)
.then(getObjE)
.then(getObjF);
}
Edit: as an additional info, you can catch any error in any of those promise by placing a catch statement here
getPromises("id")
.then(<success callback here>)
.catch(<error callback that will catch error on any of the promises>);
Meaning, once a promise fails, all the succeeding promises below wouldn't be executed and will be caught by your catch statement
Edit 2
Mistake, I just copied you code above without realizing it was an object. LOL.
promises = [
$http.get('http1'),
$http.get('http2'),
$http.get('http3'),
$http.get('http4', { params: { 'id': id } }),
$http.get('http5'),
$http.get('http6', { params: { 'id': id } })
]
Edit 1
Sorry I didn't notice the comments Jared Smith is correct.
Object keys are inherently unordered. Use an array instead.
Edit 0
Object keys wont be ordered. Use array on declaring your promises.
promises = [
$http.get('http1'),
$http.get('http2'),
$http.get('http3'),
$http.get('http4', { params: { 'id': id } }),
$http.get('http5'),
$http.get('http6', { params: { 'id': id } })
]
$q.all(promises)
.then(functions(resolves){
// resolves here is an array
}).catch(function(err){
// throw err
});

jQuery asynchronous issue undefined value

I am having difficulties with Javascript's asynchronous functions.
Code displayed below is so far almost entire code I have and I can't get it to work.
I ma trying to use Eventful API to pull some data from the server, and display it in my frontend which is created by jQuery.
So, the problem is following: function search, which is calling function Eventful.prototype.searchanje always ends up with undefined value, while a few seconds later, function searchanje console logs actual/recieved data.
I am fairly new to jQuery, and I was wondering if there is any kind of "template" for handling these things, concretely for waiting until the function returns value and then proceeding with next operations.
So far I have tried using deferred and promises, and read quite a lot tutorials and stackoverflow answers on similar subjects, but I can't get it to work properly.
Or, if deferred and promises are the right way to go, could you show me the way it is supposed to be done?
Thanks in advance
'use strict';
function Eventful(_name) {
var name = _name;
var appKey = 'appKey';
var that = this;
return {
getName: function() {
return name;
},
getAppKey: function() {
return appKey;
},
search: function() {
that.searchanje(appKey).then(function(oData) {
console.log('oData');
});
}
};
}
Eventful.prototype.searchanje = function(appKey) {
var oArgs = {
app_key: appKey,
q: 'sport',
where: 'Zagreb',
date: '2013061000-2015062000',
page_size: 5,
sort_order: 'popularity',
};
EVDB.API.call('/events/search', oArgs, function(oData) {
console.log(oData);
return oData();
});
};
On the line
EVDB.API.call('/events/search', oArgs, function(oData) {
you are passing a CALLBACK function. This function (the one that starts function(oData)) is not executed immediately. It is executed asynchronously, when the result of your API call is returned.
Try putting console.log() statements around your code, and watch the order they appear in the console. For example:
function Eventful(_name) {
var name = _name;
var appKey = 'appKey';
var that = this;
return {
getName: function() {
return name;
},
getAppKey: function() {
return appKey;
},
search: function() {
console.log('Search function called');
that.searchanje(appKey).then(function(oData) {
console.log('searchanje returned with data:');
console.log('oData');
});
}
};
}
Eventful.prototype.searchanje = function(appKey) {
console.log('function searchanje being called with appKey: ', appKey);
var oArgs = {
app_key: appKey,
q: 'sport',
where: 'Zagreb',
date: '2013061000-2015062000',
page_size: 5,
sort_order: 'popularity',
};
console.log('Calling EVDB.API.call');
EVDB.API.call('/events/search', oArgs, function(oData) {
console.log('EVDB.API callback executing with data:');
console.log(oData);
return oData();
});
console.log('finished calling EVDB.API.call');
};
I wonder if a better way ahead might be to "promisify" EVDB.API.call().
(function(app_key) {
var appKeyObj = { 'app_key': app_key },
EVDB.API.callAsync = function(path, params) {
return $.Deferred(function(dfrd) {
EVDB.API.call(path, $.extend(appKeyObj, params), function(oData) {
if (oData.error === "1") {
//translate oData.status and oData.description into a javascript Error object
// with standard .name and .message properties.
err = new Error(oData.description);
err.name = oData.status;
dfrd.reject(err);
} else {
dfrd.resolve(oData);
}
});
});
});
})('myAppKey'); //hard-coded app key
Notes:
The global namespace is avoided by :
using a self-executing anonymous wrapper
extending the EVDB.API namespace.
The "Async" suffix for a promisified method has a precendent in bluebird
From the little I understand of EVDB.API, EVDB.API.call() does just about everything, hence the single method EVDB.API.callAsync(). If necessary, further async methods could be added to EVDB.API
You might choose to use a dedicated promise lib such as bluebird or when.js in place of jQuery, In addition to including the lib, mods to the above code would be minor.
Now, instead of calling EVDB.API.call(path, params, callback) you would call EVDB.API.callAsync(path, params) to be returned a Promise.
var params = {
//app_key will be inserted automatically
q: 'sport',
where: 'Zagreb',
date: '2013061000-2015062000',
page_size: 5,
sort_order: 'popularity'
};
EVDB.API.callAsync('/events/search', params).then(function(oData) {
console.log(oData);
}, function(err) {
console.error(err);
});

Categories

Resources