Async and Await function not working properly - javascript

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()
});
}

Related

Follow on actions after asynch function completion

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=)

http request await sync in repetitive function

I have a problem with a repetitive function with an HTTP request.
When I call the getDocument() function, a result appears at the end of the console but unfortunately not in the frontend. I get a new json array by the call where also the render() function should be applied.
I render the ouput after each run of the render() function in the HTML view.
It seems to me that the HTTP.request is too slow and can't keep up with the repeating function. I have read up a bit on async await but am still having problems using it correctly.
var jsonArray = [
{
etype: 'normal',
content: 'renderContent'
},
{
etype: 'extern',
id: 'MyIdToDocument',
subcontent: [
{
etype: 'normal',
content: 'renderContent'
}
]
},
{
etype: 'normal',
content: 'renderContent'
}
]
jsonArray.forEach(object => {
this.render(object)
});
function render(object) {
if(object.etype ==='normal') {
object.subcontent.forEach( subcontentItem => {
output = this.render(subcontentItem)
})
}
if(object.etype ==='extern') {
output = this.getDocument(object.id)
}
return output;
}
function getDocument(id) {
this.httpClient.post('myAPiUrl/', id).subscribe(response => {
response.forEach(object => {
this.render(object)
});
})
}

Unexpected behavior with Object.assign

I use Object.assign() in several projects for a while and so far didn't have any unexpected experiences with the function. Today, I worked on some "auto saving" functionality and then realized that I need to "pause" (disable) the auto-saving feature for a moment to avoid double requests to the server. I went for a simple approach here to add a pause attribute to data.
So this is the solution I came up with (shortened):
let app = new Vue({
el: '#app',
data: {
pause: false,
form: {
title: '',
slug: '',
content: ''
}
},
watch: {
form: {
deep: true,
handler (newForm, oldForm) {
if (this.pause) {
return
}
this.debounceSave(newForm, oldForm)
}
}
},
methods: {
async save(newForm, oldForm) {
await this.createPost()
},
async createPost() {
try {
let response = await axios.post('https://jsonplaceholder.typicode.com/posts', {
title: 'foo',
body: this.form.content,
userId: 1
})
// disable auto saving....
this.pause = true
// I know the assignment doesnt make sense, just for testing reasons
Object.assign(this.form, {
title: response.data.title,
content: response.data.body
})
// and enable auto saving...
this.pause = false
} catch (error) {
//
}
},
},
created() {
this.debounceSave = _.debounce((n, o) => this.save(n,o), 300)
},
async mounted() {
//
}
})
I noticed tough that if I use Object.assign then the auto saving feature is not disabled and pause remains "false", hence not deactivating the auto saving feature (bad...).
I played around it and could solve the problem by using a promise:
async createPost() {
try {
let response = await axios.post('https://jsonplaceholder.typicode.com/posts', {
title: 'foo',
body: this.form.content,
userId: 1
})
// disable auto saving....
this.pause = true
// I know the assignment doesnt make sense, just for testing reasons
new Promise((resolve, reject) => {
Object.assign(this.form, {
title: response.data.title,
content: response.data.body
})
resolve()
})
.then(() => this.pause = false)
// and enable auto saving...
//this.pause = false
} catch (error) {
//
}
},
},
When using a promise I had to make sure to uncomment
// this pause = false
Which just increased my confusing as this shouldn't have any effect (I might be wrong...).
So the question now is: What is going on? Why does the initial approach not work? Why do I have to wrap it inside a Promise to make it ? Is it because of the "shallow copy" nature of Object assign? And if yes, can somebody maybe explain it?
Here's a snippet:
let app = new Vue({
el: '#app',
data: {
pause: false,
form: {
title: '',
slug: '',
content: ''
}
},
watch: {
form: {
deep: true,
handler(newForm, oldForm) {
if (this.pause) {
return
}
this.debounceSave(newForm, oldForm)
}
}
},
methods: {
async save(newForm, oldForm) {
await this.createPost()
},
async createPost() {
try {
let response = await axios.post('https://jsonplaceholder.typicode.com/posts', {
title: this.form.title,
body: this.form.content,
userId: 1
})
this.pause = true
new Promise((resolve, reject) => {
Object.assign(this.form, {
title: response.data.title,
content: response.data.body
})
resolve()
})
.then(() => this.pause = false)
//this.pause = false
} catch (error) {
//
}
},
},
created() {
this.debounceSave = _.debounce((n, o) => this.save(n, o), 300)
},
async mounted() {
//
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.21.1/axios.min.js" integrity="sha512-bZS47S7sPOxkjU/4Bt0zrhEtWx0y0CRkhEp8IckzK+ltifIIE9EMIMTuT/mEzoIMewUINruDBIR/jJnbguonqQ==" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js" integrity="sha512-WFN04846sdKMIP5LKNphMaWzU7YpMyCU245etK3g/2ARYbPK9Ub18eG+ljU96qKRCWh+quCY7yefSmlkQw1ANQ==" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/vue#2.6.12/dist/vue.js"></script>
<div id="app">
<h1>{{ pause }}</h1>
Title <input type="text" v-model="form.title"><br> Content <input type="text" v-model="form.content">
<p>{{ form }}</p>
</div>
This behavior has nothing to do with Object.assign. You need to understand how Vue’s watcher does its job under the hood.
Long story short, the handler callback, by default, is not synchronously called, it’s scheduled asynchronously till before next render or next tick.
When you synchronously set pause=true, trigger the watcher, then set pause=false, by the time the async handler is called, pause is already set back to false. This also explains why the promise version works, cus promise is async too.
If you want handler to be a sync call, you need to also set the flush: "sync" watch option in v3, in v2 I guess it’s sync: true (pls double check yourself).

Script create duplicate convensations Mongodb

I'm currently developing a conversation system with messages.
It checks if it has an active conversation ( hasConversation function) , and then determines what it should do.
If it has a conversation, then it shall only send a message, else create a conversation and then send message.
Anyway, it seems like something is wrong with my hasConversation function.
No matter what I do, it always creates two conversations, even if one exists.
It might also act like, if I call the function three times, it might create one,
then create another one, but then send the 3rd call message within the 2nd conversation.
What is wrong with my function?
It should check if both users are in a conversation.
function:
function hasConversation(userid,user2) {
return new Promise(function(resolve, reject) {
var x = [userid,user2];
var y = [user2, userid];
conversations.findOne( { members: {$all: x} }).then(function (conversation) {
// conversations.findOne({ members: x }).then(function (conversation) {
return resolve(conversation);
});
});
}
model:
var conversationsSchema = new Schema({
initiateduser : String,
name: {type:String, default: 'no name'},
members: { type: [String], default: []},
time: Number,
global: { type: Boolean, default: false},
gang: { type: Boolean, default: false},
});
a conversation is created by the following:
function createConversation(userid,user2,message) {
return new Promise(function(resolve, reject) {
var conv = new convensations();
conv.members = [userid, user2];
conv.initiateduser = userid;
conv.save(function (err,room) {
if (room._id) {
console.log("conv created, sending message");
createMessage(userid, room._id, message);
return resolve(room._id);
} else {
console.log(err);
return resolve(err);
}
});
});
}
example of calls:
Messages_model.sendNPCmessage('59312d2b329b7535b07e273c','testing','testshit?');
Messages_model.sendNPCmessage('59312d2b329b7535b07e273c','testing','testshit2?');
Messages_model.sendNPCmessage('59312d2b329b7535b07e273c','testing','testshit2?');
current output:
EDIT 1:
here is the main function calling it:
function sendNPCmessage(userid,from,message) {
console.log("checking npc conv");
return hasConversation(userid,from).then(function (haveconv) {
console.log("having conv? " + from);
console.log(haveconv);
if (haveconv) {
console.log("yes?");
return createMessage(from,haveconv._id,message).then(function (result) {
console.log("created mess?");
return result;
});
} else {
console.log("no?");
return createConversation(from,userid,message).then(function (result) {
console.log("created conv?");
return result;
});
}
});
}

How to delay a promise until async.each completes?

How do I delay a promise until an asynchronous operation has completed? I am using async and bluebird libraries. As soon as I boot up my program, the done() function returns an error that is an empty or nearly empty 'masterlist' object. Why isn't async waiting until the iterator has finished its operation?
// bundler.js
var masterlist = {
"children": []
, "keywords": []
, "mentions": 0
, "name" : "newsfeed"
, "size" : 0
}
// initialize() returns a promise with the populated masterlist
exports.initialize = function() {
return new Promise(function(resolve, reject) {
// pullBreakingNews() returns a promise with the breaking news articles
nytimes.pullBreakingNews().then(function(abstracts) {
async.map(abstracts, iterator, done);
function iterator(item, callback) {
alchemyapi.entities('text', item, {}, function(response) {
// initialize each entity with masterlist
response.entities.forEach(function(entity) {
masterlist.children[masterlist.children.length] =
{
"abstract": item
, "children": []
, "name": entity.text
, "size": 0
};
masterlist.size += 1;
masterlist.keywords.push(entity.text);
});
callback(masterlist);
});
};
function done(err, results) {
if (err) {
console.log("ERROR: ", err);
} else {
resolve(results);
}
};
});
});
};
Firehose.js is the module that calls initializer(). I believe that firehose gets run first, and the promise is called in the process.
server.js => firehose.js => bundler.js => nytimes api
// firehose.js
// Compares entities to Twitter stream, counts every match
exports.aggregator = function(callback) {
bundler.initialize().then(function(masterlist) {
t.stream('statuses/filter', { track: masterlist.keywords }, function(stream) {
// read twitter firehose for incoming tweets.
stream.on('data', function(tweet) {
var tweetText = tweet.text.toLowerCase();
// sift through each tweet for presence of entities
masterlist.children.forEach(function(parentObject) {
// if the entity exists in the tweet, update counters
if (tweetText.indexOf(parentObject.name.toLowerCase()) !== -1) {
parentObject.size += 1;
masterlist.mentions += 1;
callback(masterlist);
}
});
});
});
});
};
Thanks very much for any help.
Please don't mix callbacks and promises, only use either one.
// Do this somewhere else, it is only needed once
// it adds promise returning versions of all alchemy methods for you
Promise.promisifyAll(require('alchemy-api').prototype);
exports.initialize = function() {
return nytimes.pullBreakingNews().map(function(abstract) {
// Note that it is entitiesAsync that is a promise returning function
return alchemyapi.entitiesAsync('text', abstract, {}).then(function(response){
response.entities.forEach(function(entity) {
masterlist.children[masterlist.children.length] =
{
"abstract": abstract
, "children": []
, "name": entity.text
, "size": 0
};
masterlist.size += 1;
masterlist.keywords.push(entity.text);
});
});
}).return(masterlist);
};
Also your initialize function isn't checking if it is initialized already
The iterator's callback accepts an error as the first argument. You should pass a falsy value (like a null) there instead of masterlist if there's no error.
function iterator(item, callback) {
alchemyapi.entities('text', item, {}, function(response) {
// initialize each entity with masterlist
response.entities.forEach(function(entity) {
masterlist.children[masterlist.children.length] =
{
"abstract": item
, "children": []
, "name": entity.text
, "size": 0
};
masterlist.size += 1;
masterlist.keywords.push(entity.text);
});
callback(null, masterlist);
});
};

Categories

Resources