jQuery asynchronous issue undefined value - javascript

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

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

Infinite functions calls like 'string'.replace().replace()

I'm not really sure how to explain so I will start with the output.
I need to return this:
{
replies:
[
{ type: 'text', content: 'one' }
{ type: 'text', content: 'two' }
{ type: 'text', content: 'three' }
],
conversation: {
memory
}
}
And I wanted to return that through in-line statement.
So I would like to call something like:
reply.addText('one').addText('two').addText('three').addConversation(memory)
Note that addText can be called infinite times while addConversation can be called only one time. Also conversation is optional, in that case, if conversation is absent the conversation object should not appear in the output.
To create a custom structured object use a constructor, say Reply.
To call instance methods on the return value of method calls, return the instance object from the method.
Choices to prevent multiple additions of conversation objects include throwing an error (as below) or perhaps logging a warning and simply not add additional objects after a first call to addConversation.
Write the code to implement the requirements.
For example using vanilla javascript:
function Reply() {
this.replies = [];
}
Reply.prototype.addText = function( content) {
this.replies.push( {type: "text", content: content});
return this;
}
Reply.prototype.addConversation = function( value) {
if( this.conversation) {
//throw new Error("Only one conversation allowed");
}
this.conversation = {conversation: value};
return this;
};
Reply.prototype.conversation = null;
// demo
var reply = new Reply();
reply.addText( "one").addText("two").addConversation("memory?");
console.log( JSON.stringify( reply, undefined," "));
(The console.log uses JSON stringify to avoid listing inherited methods)
A possible implementation is to create a builder as follows:
function create() {
const replies = []; // store all replies in this array
let conversation; // store the memory here
let hasAddConversationBeenCalled = false; // a state to check if addConversation was ever called
this.addText = function(content) {
// add a new reply to the array
replies.push({
type: 'text',
content
});
return this; // return the builder
};
this.addConversation = function(memory) {
if (!hasAddConversationBeenCalled) { // check if this was called before
// if not set the memory
conversation = {
memory
};
hasAddConversationBeenCalled = true; // set that the memory has been set
}
return this; // return the builder
}
this.build = function() {
const reply = {
replies
};
if (conversation) { // only if converstation was set
reply.conversation = conversation; // add it to the final reply object
}
return reply; // finally return the built respnse
}
return this; // return the new builder
}
You can then use it as follows:
const builder = create();
const reply = builder.addText('one').addText('two').addText('three').addConversation({}).build();
Here is a link to a codepen to play around with.
If you specifically want to add assemble this via multiple function calls, then the builder pattern is your best bet, as vader said in their comment.
However, if the goal is to simply create shorthand for concisely building these objects, it can be done using a function that takes the list of text as an array.
const buildObject = (textArray, memory) => {
return Object.assign(
{},
{
replies: textArray.map(x => {
return {
type: 'text',
value: x
}
})
},
memory ? {conversation: memory} : null
)
}
var memory = { };
//with memory
console.log(buildObject(['one', 'two', 'three'], memory ))
//without memory
console.log(buildObject(['one', 'two', 'three']));
Fiddle example: http://jsfiddle.net/ucxkd4g3/

Call Nested Function Within Prototype Method And Return Promise

Use Case: I have users that need a relatively simple dot-notation that also handles a significant amount of asynchronicity. I have been tinkering with a pattern that is most likely verging into a closure. The pattern includes:
Initial function definition
Prototype method declaration
Functions within prototype that typically will return a value or promise.
Outcome: I would like a user to be able to access these methods in the format:
ParentFunction.prototype_method.nested_function(args);
Problem
Right now I am seeing various undefined issues and since I am designing this to be a rather large API implementation, I am unsure whether this pattern is most appropriate. Since I come from a Java background I prefer this type of approach if at all possible, and my end goal is to make the implementation of the API as readable and accessible as possible for junior users.
Sample Source
//Parent function
var Actor = function(model, timings, options) {
this.model = {
"node": node
};
this.timings = timings;
this.options = options;
};
//Sample prototype method
Actor.prototype.eventController = function() {
var self = this;
var clickEvent = function() {
return new Promise(function(resolve, reject) {
setTimeout(function() {
try {
$(self.model.node).trigger('click');
resolve(true);
} catch (error) {
resolve(false);
}
}, self.timings.click);
});
}
var mouseOverEvent = function() {
return new Promise(function(resolve, reject) {
setTimeout(function() {
try {
$(self.model.node).trigger('mouseover');
resolve(true);
} catch (error) {
resolve(false);
}
}, self.timings.mouseover);
});
}
//Instantiate parent function
var actor = new Actor();
//Access nested function within prototype method then handle promise result
actor.eventController.clickEvent.then(...);
Rather than messing about with prototypes, what if your constructor function set the properties you need?
// dummy version for testing
const $ = (elt) => ({trigger: function(evt) {console.log(`${evt} on ${elt}`)}})
const makeHandler = (obj, evtName) => () => new Promise((resolve, reject) => {
setTimeout(() => {
try {
$(obj.model.node).trigger(evtName);
resolve(true);
} catch(error) {
resolve(false);
}
}, obj.timings[evtName]);
});
function Actor(foo) {
this.model = {node: foo};
// don't know where these would come from. Hard-coding here.
this.timings = {
click: 150,
mouseover: 100
};
this.eventController = {
clickEvent: makeHandler(this, 'click'),
mouseOverEvent: makeHandler(this, 'mouseover')
}
}
var actor1 = new Actor('abc');
var actor2 = new Actor('def');
actor1.eventController.clickEvent().then(console.log, console.log);
actor2.eventController.mouseOverEvent().then(console.log, console.log);
You could also replace that with a factory function:
function actor(foo) {
const obj = {
model: {node: foo},
timings: {
click: 150,
mouseover: 100
}
};
obj.eventController = {
clickEvent: makeHandler(obj, 'click'),
mouseOverEvent: makeHandler(obj, 'mouseover')
};
return obj;
}
var actor1 = actor('abc');
var actor2 = actor('def');
(And of course this could all be cleaned up a bit with a dose of ES6 arrow functions.)

How to programatically chain two js functions?

I'm trying to solve how I can to chain several js functions (two in this case). I have one javascript function to save() an another for saveAndPrint(). I need to chain the call because I need the id generated in save() to forward it for printing in the second one. Probably I just could use only one function to do both things but I'd like to learn how to do this. At the moment I just getting an 'undefined' because second function starts before the first one finishes.
Here is the code:
$scope.save = function () {
var receipt = {
documentType: 'RCI',
expirationDate: new moment().format('YYYY-MM-DD'),
person: {id: $scope.financeDocuments[0].person.id},
payments: getPayments(),
promotions: [],
creationDate: new moment().format('YYYY-MM-DD'),
compensatedDocuments: getDocumentsToPay()
};
financeDocumentService.save(receipt, function (response) {
receipt = response;
$uibModalInstance.close(response);
}).$promise.then(function (data) {
return receipt;
});
};
$scope.saveAndPrint = function() {
var document = $scope.save();
$window.location.href = "#/finance/receipt_show/"+document.id;
};
Thanks a lot!
First return the promise:
$scope.save = function () {
var receipt = {
documentType: 'RCI',
expirationDate: new moment().format('YYYY-MM-DD'),
person: {id: $scope.financeDocuments[0].person.id},
payments: getPayments(),
promotions: [],
creationDate: new moment().format('YYYY-MM-DD'),
compensatedDocuments: getDocumentsToPay()
};
//RETURN the promise
͟r͟e͟t͟u͟r͟n͟ financeDocumentService.save(receipt, function (response) {
receipt = response;
$uibModalInstance.close(response);
}).$promise.then(function (data) {
return receipt;
});
};
Then chain from the promise:
$scope.saveAndPrint = function() {
var promise = $scope.save();
promise.then(function(receipt) {
$window.location.href = "#/finance/receipt_show/"+document.id;
});
};
For more information,
AngularJS $q Service API Reference - Chaining Promises
You`re Missing the Point of Promises

Callback was already called in async module

I have the following array:
var files = [
{
name: 'myfile.txt'
},
{
name: 'myfile2.txt'
}
];
I'm using async for access this objects and send them to extraction, like this:
extraction function:
var extraction = function(file, callback) {
// extract
// return text
callback(text);
};
Using async
var fn = function(files, callback) {
Async.mapLimit(files, 3000, function(file, done) {
extraction(file, function(texts) {
done(texts);
});
}
}, function(texts) {
callback(texts);
}
How i need call:
fn(files, function(texts) {
console.log(texts); // new objects, now with text's
});
Like this, i'm receiving the following error:
Done was already called.
This happens because i can't call the done inside the async two ways, and i'm doing that.
To around this problem, i'm doing this inside the Async:
if (files.length === texts.length) {
done(texts);
}
So will compare. Files size is the same as texts size? Yes? So you can call the done.
This will work perfectly. The problem with this is that i can't pass a error argument inside a else statement, like this:
if (files.length === texts.length) {
done(null, texts);
} else {
done('ERROR'!);
}
First because would be call in every loop, so will cause the same error:
Done was already called.
And even if i could, would break the loop because will be called in the first looping.
How can i fix this? I need a light here :/
This code is not braced correctly and the callback arguments were not in the proper position in a couple of places:
var fn = function(files, callback) {
Async.mapLimit(files, 3000, function(file, done) {
extraction(file, function(texts) {
done(texts);
});
}
}, function(texts) {
callback(texts);
}
It should be:
var fn = function (files, callback) {
Async.mapLimit(files, 3000, function (file, done) {
extraction(file, function (texts) {
done(null, texts);
});
}, function (err, texts) {
callback(texts);
});
}
Async.mapLimit() takes four arguments. You were not passing the fourth argument correctly. In fact, if you paste your code into http://jshint.com/, it will show you where the errors are (frankly I'm surprised that the code even ran).
Here's an article that shows you some Async.mapLimit() coding examples that work just like you are trying to do: Concurrency level (async.mapLimit)

Categories

Resources