JQuery Return the Object with the Promise - javascript

I've got multiple pages of widgets. The contents of the widgets are static, though their display can change. I need to be able to store these, so I'm building an multi-dimensional object.
I need to store the object and display the object.
Here's a simplified version of my factory:
const createWidget = ({
id = undefined,
name = 'new widget',
style = 'vertical_list',
html = undefined,
} = {}) => ({
id,
name,
style,
data,
constructWidget () {
return $.ajax({
url: '/my/url',
type: 'POST',
data: {
'_token' : window.xsrf_token,
'widget_id': this.id,
},
dataType: 'json',
}).then(reponse => {
let widget = response.widget;
this.name = widget.name;
this.style = widget.style;
this.html = widget.html;
});
},
})
That hydrates the object with everything just fine, but I can't get the object back.
let book = {};
let page = 'example page';
book[page] = book[page] || {};
let widget_id = 88;
// TRYING TO STORE
// this just give me the promise and I can't get at the object :(
book[page][widget_id] = CreateWidget({id: widget_id}).constructWidget();
// TRYING TO DISPLAY
let widget = book[page][widget_id];
$(`#current_page`).append(`
<div id="widget_${widget.id}" class="${style}">
<b>${widget.name}</b>
${widget.html}
</div>`
);
So? Why is this approach stupid and how do I fix it? I've tried messing with promises a bunch, but without access to the hydrated object I can't seem to do a dang thing.

you need to wait for these promises somehow
simplest (well, the shorter code example at least) way is to use async/await
async function() {
book[page][widget_id] = await CreateWidget_1({id: widget_id}) //the promise :(
book[page][widget_id] = await CreateWidget_2({id: widget_id}) //maybe...
$(`#current_page`).append(`<div>${book[page][widget_id]}</div>`); //nope, that'll be undefined because the promise isn't complete this isn't an asynch approach, dummy
or the longer promises based
Promise.all[CreateWidget_1(...), CreateWidget_2(...)])
.then(function() { $(`#current_page`).append(...) })
Also, chaining is one of the most important concepts of promises, to properly use promise, they need to be returned, both your functions need to return the promise (only one returns it)

The issue was that I needed to return this as part of the promise. I wasn't doing that. Huge thanks to #JaromandaX for that.
Returning the promise and the object means that I continue on a asynchronous path. Simplified example answer below.
const createWidget = ({
id = undefined,
name = 'new widget',
style = 'vertical_list',
html = undefined,
} = {}) => ({
id,
name,
style,
data,
constructWidget () {
return $.ajax({
url : '/my/url',
type: 'POST',
data: {
'_token' : window.xsrf_token,
'widget_id': this.id,
},
dataType: 'json',
}).then(reponse => {
let widget = response.widget;
this.name = widget.name;
this.style = widget.style;
this.html = widget.html;
return this; // <-- here's what I was missing.
});
},
})
let book = {};
let page = 'example page';
book[page] = book[page] || {};
let widget_id = 88;
CreateWidget({id: widget_id})
.constructWidget()
.then(widget => {
book[page][widget_id] = widget;
$(`#current_page`).append(`
<div id="widget_${widget.id}" class="${style}">
<b>${widget.name}</b>
${widget.html}
</div>`
);
})
.fail(error => console.error);
A pox upon the article that said a return inside the then was meaningless!

Related

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/

undefined object in reactjs application

I have a container that holds search bar inside a form element consisting of two inputs, from and to, and a button.
On my form submit function, I create an OBJECT called query and it equals to:
const query = {
from : this.state.from,
to : this.state.to
};
then I pass this query object as an argument to an action that have created :
this.props.fetchPlaces(query);
In my action.js inside my fetchPlaces I have :
var skypicker = {
flyFrom : query.flyFrom,
flyTo : query.toCity,
};
I want to be able to pass flyFrom and flyTo to an api that later returns flights from flyFrom to flyTo which return results as JSON!
ofcourse i have to parse them to the URl, but at the current state flyFrom and flyTo are undefined, Am I doing this currectly?
You're accessing properties that you haven't set. The names differ between query and skypicker.
Try creating skypicker like this:
var skypicker = {
flyFrom: query.from,
flyTo: query.to
};
It’s query.from and query.to but not query.flyFrom and query.toCity
Check right one below
var skypicker = {
flyFrom : query.from,
flyTo : query.to
};
I generally do this
request body:
let data = {};
data.firstName = this.state.firstName;
data.lastName = this.state.lastName;
data.email = this.state.email;
data.password = this.state.password;
data.sex = this.state.sex;
this.props.registerUser(data);
action call:
export function registerUser(data) {
return dispatch => {
dispatch(registerUserRequest());
return ajax.post(URL_PREFIX + '/auth/register/', data)
.then(res => {
dispatch(registerUserSuccess(res.data))
setTimeout(function(){
dispatch(push('/login'))
}, 2000)
})
.catch(errors => {
dispatch(registerUserError(errors))
})
}
}

Javascript function to return Elasticsearch results

I'm trying to write a JavaScript function that returns results of an Elasticsearch v5 query. I can't figure out where and how to include 'return' in this code. With the following, segmentSearch(id) returns a Promise object,{_45: 0, _81: 0, _65: null, _54: null}.
_65 holds an array of the correct hits, but I can't figure out how to parse it. The console.log(hits) produces that same array, but how can I return it from the function?
var elasticsearch = require('elasticsearch');
var client = new elasticsearch.Client({
host: 'localhost:9200',
log: 'trace'
});
segmentSearch = function(id){
var searchParams = {
index: 'myIndex',
type: 'segment',
body: {
query: {
nested : {
path : "properties",
query : {
match : {"properties.source" : id }
},
inner_hits : {}
}
}
}
}
return client.search(searchParams).then(function (resp) {
var hits = resp.hits.hits;
console.log('hits: ',hits)
return hits;
}, function (err) {
console.trace(err.message);
});
}
I would instanitate a new array outside of your client.search function in global scope and array.push your 'hits' Then access your newly filled array.
let newArr = [];
client.search(searchParams).then(function (resp) {
for(let i = 0; i < resp.hits.hits.length; i++){
newArr.push(resp.hits.hits[i]);
}
console.log('hits: ',newArr)
return newArr;
}, function (err) {
console.trace(err.message);
});
First of all, elasticsearch js client is working with Promise ( I think using callback is also possible).
Using Promise is a good way to handle asynchronous computation.
In your question, you have already done something with a promise:
var search = function(id)
{
var searchParams = { /** What your search is **/}
return client.search(searchParams)
}
This call is returning a Promise.
If we consider handleResponse as the function in your then
var handleResponse = function(resp)
{
var hits = resp.hits.hits;
console.log('hits: ',hits)
return hits; //You could send back result here if using node
}
In your code, handleResponse is called once the promise is fullfield. And in that code you are processing data. Don't forget you are in asynchronious, you need to keep working with promise to handle hits.
By the way, in your question, what you have done by using "then" is chaining Promise. It is normal to have Promise.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then
Promise.prototype.then is returning a Promise.
var segmentSearch = function ( id )
{
return search
.then( handleResponse //here you got your hit )
.then( function ( hits )
{
console.log(hits) //and here you have done something with hits from search.
})
}
I neglected to post my fix, sorry about that. The sequence is to create searchParams, perform client.search(searchParams) which returns a Promise of hits, then process those hits:
segmentSearch = function(obj){
// retrieve all segments associated with a place,
// populate results <div>
let html = ''
var plKeys = Object.keys(obj)
var relevantProjects = []
for(let i = 0; i < plKeys.length; i++){
relevantProjects.push(obj[plKeys[i]][0])
var searchParams = {
index: 'myIndex',
type: 'segment',
body: {
query: {
nested : {
path : "properties",
query : {
match : {"properties.source" : id }
},
inner_hits : {}
}
}
}
}
client.search(searchParams).then(function (resp) {
return Promise.all(resp.hits.hits)
}).then(function(hitsArray){
...write html to a <div> using hits results
}
}

How to resolve function and call other function in angular

I have two functions and I want to call one function after other only first function is resolved successfully.
$scope.createNewWorkModule = function (data, selectedUser) {
$scope.workOrderInfo = $localStorage.workOrderInfo;
$scope.createNewWorkOrder($scope.workOrderInfo).then(function (response) {
$scope.workOrderId = $localStorage.workOrderId;
var newWM = {
name: data.name,
};
$scope.myPromise = operationsFactory.createWorkModule(angular.toJson(newWM)).success(function (response) {
$rootScope.workModules = response.result;
return $scope.workOrder;
})
});
};
$scope.createNewWorkOrder = function (data) {
var newWD = {
name: data.name,
description: data.description,
user_id: $cookies.userId
};
$scope.myPromise = operationsFactory.createWorkOrder(angular.toJson(newWD)).success(function (response) {
$localStorage.workOrderId = response.result.id;
})
return $localStorage.workOrderId;
};
I am trying to call $scope.createNewWorkOrder with .then but it is giving error that
TypeError: Cannot read property 'then' of undefined
at Scope.$scope.createNewWorkModule (work-order-create-controller.js:36)
at $parseFunctionCall (angular.js:12158).
How do I fix this?
Update:
Unable to update the view with data retrieved:
<div class="panel panel-default">
<div class="panel-heading font-bold">
<span>List of Work Modules</span>
</div>
<ul ng-repeat="wm in workModules" style="list-style: none">
<li>{{wm.name}}</li>
<li>{{wm.description}}</li>
</ul>
</div>
Your asynchronous functions should always return the promises.
Make sure the then success functions always return the values needed by the next handler in the chain (and don't use the deprecated .success method as that doesn't chain correctly, always use .then but be aware of the slightly different parameter is gets from $http results).
Also try not to put things into $scope unless you really need them there, probably many of your functions can be just functions and the values could be local variables in the controller.
Note that once you are using .then you can issue a new call at the end of one success handler and process the result with another .then at the same level, no need to nest things further.
$scope.createNewWorkModule = createNewWorkModule; // Do you need both this
$scope.createNewWorkOrder = createNewWorkOrder; // and this?
function createNewWorkModule(data, selectedUser) {
$scope.workOrderInfo = $localStorage.workOrderInfo;
return createNewWorkOrder($scope.workOrderInfo)
.then(function (workOrderId) {
$scope.workOrderId = workOrderId;
var newWM = {
name: data.name,
};
return operationsFactory.createWorkModule(angular.toJson(newWM));
})
.then(function (response) {
// here we have response from the createWorkModule call
$rootScope.workModules = response.data.result;
return $scope.workOrder;
});
};
function createNewWorkOrder(data) {
var newWD = {
name: data.name,
description: data.description,
user_id: $cookies.userId
};
return operationsFactory.createWorkOrder(angular.toJson(newWD))
.then(function (response) {
var workOrderId = $localStorage.workOrderId = response.data.result.id;
return workOrderId;
})
};
You are getting error then is not a function, because the value returned is undefined as $localStorage.workOrderId is resolved async. Ideally you should return a promise.
return operationsFactory.createWorkOrder(angular.toJson(newWD)).success(function (response) {
$localStorage.workOrderId = response.result.id;
return $localStorage.workOrderId;
});

How can I pass an data from factory to my controller in angularjs?

I'm trying to move all the business logic from my controller to the factory, but I'm having some trouble passing fields data.
factory.js
app.factory("Quote", function ($resource) {
// TODO: this shouldn't start with /en/
var quoteStatus = [];
var quoteLanguage = [];
var Quote = $resource("/en/quote/api/quote/:id", {}, {
retrieve: {
method: 'GET',
params: {},
isArray: true
},
query: {
method: 'GET',
params: {},
isArray: true,
url: '/en/quote/api/quote/'
},
fields: {
method: 'GET',
url: '/en/quote/api/quote/fields/ '
},
update: {
method: 'PATCH',
},
});
Quote.fields().$promise.then(function (fields) {
var tempObj = [];
for (key in fields.status) {
// must create a temp object to set the key using a variable
tempObj[key] = fields.status[key];
quoteStatus.push({
value: key,
text: tempObj[key]
});
}
for (key in fields.language) {
// must create a temp object to set the key using a variable
tempObj[key] = fields.language[key];
quoteLanguage.push({
value: key,
text: tempObj[key]
});
}
//$scope.addLanguage($scope.language);
Quote.status = quoteStatus;
Quote.language = quoteLanguage;
});
return Quote;
});
controller.js
$scope.quoteStatus = Quote.status;
However this is not working since $scope.quoteStatus is undefined. What am I missing?
Thanks in advance.
You can't expect async operation to behave in synchronous way.
Basically when controller inject Quote in its factory function that time Quote service object gets created & then calls Quote.fields(). hen you ask Quote.status inside a controller will always return undefined. You are not maintaining promise anywhere so that controller will come to know that the data is ready or not.
I think you should introduce $q.when flag there to check the Quote.fields() operation completed or not & then do get the desired variable there.
For implementing above mention thing you need to store the promise of Quote.fields() call somewhere in service. like below
var quoteFieldsPromise = Quote.fields().$promise.then(function (fields) {
/// the code will be same here
};
Then add new method which will hold of quoteFieldsPromise promise object and return the value of quoteStatus & quoteLanguage.
var getQuoteDetails = function(){
$q.when(quoteFieldsPromise).then(function(){
return { quoteStatus: Quote.quoteStatus, quoteLanguage: Quote.quoteLanguage };
})
}
But the way you have returned whole Quote object, which only has $resource object which needs to be changed. I mean to say that the getQuoteDetails method which I've created can not be return with Quote object. So I'd rather rather refactor service to below.
Service
app.factory("Quote", function($resource, $q) {
// TODO: this shouldn't start with /en/
var quoteStatus = [], //kept private if needed
quoteFieldsPromise,
quoteLanguage = [];//kept private if needed
var QuoteApi = $resource("/en/quote/api/quote/:id", {}, {
//inner code is as is
});
//preserve promise of .fields() call
quoteFieldsPromise = Quote.fields().$promise.then(function(fields) {
//inner code is as is
//below lines are only changed.
Quote.status = quoteStatus;
Quote.language = quoteLanguage;
});
var getQuoteDetails = function() {
return $q.when(quoteFieldsPromise).then(function() {
return {
quoteStatus: quoteStatus,
quoteLanguage: quoteLanguage
};
})
};
return {
QuoteApi: QuoteApi,
getQuoteDetails: getQuoteDetails
};
});
Controller
Quote.getQuoteDetails().then(function(quoteDetails){
$scope.quoteStatus = quoteDetails.quoteStatus;
$scope.quoteStatus = quoteDetails.quoteLanguage;
});

Categories

Resources