i write this code but i don`t now why my console.log give me undefined and how can i get value of prototype properties or functions
(function() {
function Users() {}
Users.prototype.getUser = function() {
$.getJSON('/usersList/users.json', function(request) {
Users.prototype.allUsers = request.users;
});
}
var users = new Users();
users.getUsers();
console.log(users.allUsers);
}
())
What i wont to achieve is to have this user list as my object property like User.allUsers in some array.
Thanks
This is because $.getJSON is asynchronous.
When you create an object from User prototype, $.getJSON has to call its callback yet.
In the other hand, you're trying to simulate global variables adding data properties to a given prototype. This is a bad practice and, at the end of the day, you've already realized that you got stuck on this approach.
What you need an initialization code executed whenever the site or app is started so those allUsers are called once and injected everywhere:
const initApp = () => Promise.all([
$.getJSON("/someData/users.json")
// other comma-separated async functions which return promises
])
initApp().then(([allUsers, otherData, yetAnotherData]) => {
callSomeFunctionWhichRequiresAllUsers(allUsers)
})
Related
I need to instantiate an object asynchronously. But the constructor method cannot be declared async async constructor(){} and be called like so async new MyClass(). That would be strange anyway. Therefore, some kind of factory is needed. I also want to hide the constructor. This would be very easy to achieve if the constructor could be declared private: #constructor. But even with ES2022 private class features this is not possible. However, there are several patterns to solve these two problems. I will list 3 of them (in the context of ES6 modules).
I would like to know which pattern, if any, are considered good and which are not. Or if there are even better solutions, please let me know.
Method I: static boolean (without a factory)
let calledInit = false;
export default class MyClass {
constructor(data) {
if (calledInit === false)
throw Error('private constructor');
calledInit = false;
// member initializations here
}
static async initialize() {
calledInit = true;
// do some async stuff here
return new MyClass(data);
}
}
Method II: closure and singleton-factory
const MyClass = (function() {
class MyClass {
constructor(data) {
// member initializations here
}
}
return {
initialize: async function() {
// do some async stuff here
return new MyClass(data);
}
}
})();
Object.freeze(MyClass);
export default MyClass;
use them both like so:
const obj = await MyClass.initialize();
Method III: factory function
class MyClass {
constructor(data) {
// member initializations here
}
}
export default async function MyClassInitializer() {
// do some async stuff here
return new MyClass(data);
}
use it like so:
const obj = await MyClassInitializer();
Edit: To clarify, I believe hiding the constructor is beneficial. Mainly for debugging purpose. Let's say the constructor is public and someone else is instantiating the object using the new operator. It always fails with obscure error messages and she/he can't make sense of it. All this, because she/he didn't carefully read the docs which clearly states to use the initialize() method to create an instance. This could easily have been prevented by hiding the constructor in the first place.
Disclaimer This is my opinion
Instantiating an object asynchronously is not really a thing for a good reason.
The purpose of the constructor is to construct your object.
You should not put something like load operations from a file, API etc. in the constructor.
Put them into a separate function and use the result as an argument to your class.
async () => {
const data = await fetch(...) // get data / do some sideeffect
const myObject = new MyObject(data)
}
This is also usually desirable since you can update some state to track the progress of the request. Or you might want to cache it and use it in multiple places.
async () => {
state = "LOADING"
const data = await fetch(...) // get data / do some sideeffect
state = "SUCCESS"
const myObject = new MyObject(data)
}
Hiding the constructor is not really needed since you get an error when you do not pass data to it. And the user of your class should know what to pass. If you use typescript, this would be even more strict.
Sure you can put this in one initialize function as in method 3.
I'm looking to use sinon stub to test the method chain below:
driver.manage().window().setSize()
I found a related question that explains how to access one method down the chain, however this does not seem to give me access to any additional methods.
t.context.webdriver = sinon.stub(new WebDriver)
sinon.stub(t.context.webdriver, "manage", () => {
return {
window: sinon.stub().returns();
};
})
which returns the error
Error: this._driver.manage(...).window(...).setSize is not a function
How to I stub multi-level method chains?
I'm not sure what your trying to to test, but the error is coming from the fact that the object your stub is returning doesn't have a window() function or a setSize(). Chains work because each part of the chain returns something with a method that matches the next call. So if you stuff something early in the chain, you need to makes sure what you return has those methods. Maybe that involves passing back the original return, or maybe you fake the whole chain.
Here's an example that at least won't throw:
const sinon = require('sinon')
// some fake object that maybe looks like what you have
let driver = {
manage(){ return this},
window() { return this},
setSize() {console.log("size set")}
}
// stubb manage and now you're resposible for the whole chain
sinon.stub(driver, "manage").callsFake(() => {
console.log("called")
return {
window(){
return { setSize: sinon.stub().returns() }
}
};
})
Of course, there are a lot of variations possible depending on what you're trying to test.
I want to create a class whose duty is to poll data sources, collate information into an array of 'alert' objects, and then deliver a subset of those alerts to any other class that wants them.
Because polling happens asynchronously (I'm requesting data from a web service) then I assume that what I actually need to return is a promise which, when fulfilled, will give the correct subset of Alert objects.
But clearly I don't understand how to do this, because the method that is supposed to return the promise returns something else.
Here's my code so far. As you can see, I'm trying to store the promise in an instance attribute and then retrieve it:
export class AlertCollection {
constructor() {
this.alerts = null;
}
// poll the data sources for alert data; store a promise that resolves
// to an array of alerts
poll() {
this.alerts = this.pollTeapot()
.then( (arr) => {this.pollDeliverance(arr);} );
}
// return a promise that fulfils to an array of the alerts you want
filteredAlerts(filter) {
return this.alerts; // not filtering for now
}
// return a promise that fulfills to the initial array of alerts
pollTeapot() {
let process = (json) => {
json2 = JSON.parse(json);
return json2.map( (a) => new Alert(a) );
};
message = new MessageHandler("teapot", "alerts")
return message.request().then( (json) => {process(json);} );
}
// Modify the alerts based on the response from Deliverance.
// (But for the time being let's not, and say we did.)
pollDeliverance(alerts) {
return alerts;
}
}
message.request() returns a promise from the web service. That works. If I snapshot the process function inside pollTeapot() I get the right data.
But, if I snapshot the return value from filteredAlerts() I don't get that. I don't get null either (which would at at least make sense, although it would be wrong.) I get something like { _45: 0, _81: 0, _65: null, _54: null }.
Any pointers would be very much appreciated at this point. (This is in React Native, by the way, if that helps.)
I am not sure if I understood your problem fully, but I will try to give you an generic solution to chaining promises one after another.
someAsyncFunction().then(dataFromAsync1 => {
return anotherAsyncFunction(dataFromAsync1).then(dataFromAsync2 => {
return doSomethingWithData(dataFromAsync1, dataFromAsync2);
});
});
This is going to be a hard one to describe - I have a working example but it's convoluted in that I've had to "mock up" all of the async parts, and use function classes rather than the class keyword - but the idea is the same!
There are 2 parts to this answer.
It does not make sense to store alerts as an instance variable. They are asynchronous, and wont exist until after the async calls have completed
You'll need to chain all of your behaviour onto the initial call to poll
In general, you chain promises on to one another like this
functionWhichReturnsPromise()
.then(functionPointer)
.then(function(result){
// some functionality, which can return anything - including another promise
});
So your code would end up looking like
var alertCollection = new AlertCollection()
alertCollection.poll().then(function(alerts){
//here alerts have been loaded, and deliverance checked also!
});
The code of that class would look along the lines of:
export class AlertCollection {
constructor() {
}
// poll the data sources for alert data; store a promise that resolves
// to an array of alerts
poll() {
return this.pollTeapot()
.then(filteredAlerts)
.then(pollDeliverance);
}
// return a promise that fulfils to an array of the alerts you want
filteredAlerts(alerts) {
return alerts; // not filtering for now
}
// return a promise that fulfills to the initial array of alerts
pollTeapot() {
let process = (json) => {
json2 = JSON.parse(json);
return json2.map( (a) => new Alert(a) );
};
message = new MessageHandler("teapot", "alerts")
return message.request().then(process);
}
// Modify the alerts based on the response from Deliverance.
// (But for the time being let's not, and say we did.)
pollDeliverance(alerts) {
return alerts;
}
}
A few notes
filteredAlerts can do whatever you like, so long as it returns an array of results
pollDeliverance can also do whatever you like - if it needs to call another async method, remember to return a promise which resolves to an array of alerts - perhaps updated from the result of the async call.
I have created a JSFiddle which demonstrates this - using a simple getJSON call to replicate the async nature of some of this. As I mentioned, it is convoluted, but demonstrates the process:
Live example: https://jsfiddle.net/q1r6pmda/1/
I'm creating a factory to take a userId from one page, make a call to a REST API, and return the results on the following view. My initial attempts were largely taken from this answer but - unsurprisingly - I keep getting caught in a situation where the doesn't respond in time and the get() method returns an empty array.
Here's the factory itself
app.factory('GetMessages', function() {
var messages = []
function set(userId) {
Restangular.all('/api/messages/').getList({'_id': userId}).then(function(docs){
messages = docs
})
}
function get() {
return messages;
}
return {
set: set,
get: get
}
});
For what it's worth I'm having no trouble getting the userId into the factory as it's just passed in on a function like this
view:
<a ng-click='passToFactory(message.user.id)' href='/home/inbox/reply'>Reply</a>
controller:
$scope.passToFactory = function(id) {
GetMessages.set(id);
};
and the controller for the following view is just
$scope.messages = GetMessages.get()
The issue I'm having is that after the factory returns the empty set no further changes from the factory are recognized (even though after time elapses it does get the proper response from the API) and $scope.messages remains empty.
I've attempted to move the API call to the get method (this hasn't worked as the get method often does not get the userId in time) and I can't find a way to use a promise to force get() to wait on set() completing.
I'd prefer to keep using Restangular in the eventual solution but this is a small thing that has taken too much time so any fix works.
I'm fairly new to Angular so I'm sure there's something totally obvious but right now I'm just lost. Thanks.
The race condition that you have is that the function inside the .then method is executed asynchronously after the call to the set function. If the get function executes before the $q service fulfills the promise, the get function returns an empty array.
The solution is to save the promise and chain from the promise.
app.factory('GetMessages', function() {
var promise;
function set(userId) {
promise = Restangular.all('/api/messages/').getList({'_id': userId});
}
function get() {
return promise;
}
return {
set: set,
get: get
}
});
In your controller, chain from the promise.
GetMessages.get.then( function (docs) {
$scope.messages = docs;
}) .catch ( function (error) {
//log error
};
For more information on chaining promises, see the AngularJS $q Service API Reference -- chaining promises.
You are breaking the reference to the original messages array when you reassign it.
Try:
Restangular.all('/api/messages/').getList({'_id': userId}).then(function(docs){
messages.concat(docs) ; // keep same array reference
});
Simple example to explain why it isn't working
var arr = [];
var x = arr;
arr = [1,2,3]; // is now a different array reference
console.log(x); // is still empty array. x !== arr now
cherlietfl is right.
The problem is that you break the reference to the messages array since you assign a new array to messages inside your get function. But concat is doing this as well.
Try this:
Restangular.all('/api/messages/').getList({'_id': userId}).then(function(docs){
messages.splice(0, messages.length); // clear the array
messages.push.apply(messages, docs); //add the new content
});
Try assigning you function to the scope. Then call that function in the model. Like so:
// controller
$scope.getMessages = GetMessages.get;
View:
<div ng-repeat="message in getMessages()"></div>
This way when the request call finishes and the digest cycle goes through the watchers again, the get function will be called and you will get your messages.
This question already has answers here:
How do I access previous promise results in a .then() chain?
(17 answers)
Closed 8 years ago.
I need to chain some asynch actions. I'm trying to write Parse.com cloud code which uses express. (I think express is where the promises support originates). I see why promises are valuable, but am still unsure about a couple things. The first is how to collect the results of sequenced actions. The code below illustrates:
function doAsnychThingsInSequence(params) {
return doThing0(params).then(function(resultOfThing0) {
// thing1 depends on resultOfThing0
doThing1(resultOfThing0);
}).then(function(resultOfThing1) {
// here is where I am confused.
// thing2 depends on the results of thing0 and thing1
doThing2(resultOfThing0 /* out of scope?? */, resultOfThing1);
}, function(error) {
// handle error
});
}
After thing1 is done, I need the results of both actions. I think I can allocate a variable at the top of the function and assign it to the first result in the first callback, is that the right way? I guess, at the heart of my confusion is question two...
return doThing0(params0).then(function(resultOfThing0) {
doThing1(resultOfThing0);
// what does return mean here? does what I return here relate to the
// parameters of the next function?
return "foo";
}).then(function(param) {
// what is in param? is it "foo"?
}, function(error) {
});
Promises can be imagined as a stream processing: one function gets input, does something with it and pass it to the next function in the chain.
So if you need to pass input (for the chain) parameters further you should include them into
the output data so next one in the chain can use them:
function doThing1(params1) {
...
return [params1, result1];
}
function doThing2(params1, params2) {
...
}
As you've mentioned you can use some variable outside doThing1 and doThing2 but it will make these functions statefull that may lead to side effects of various kinds. Not desired in general for async processing.
The function supplied to then() must return a value, whether that value is the value to be used, or a promise for an upcoming value. Either way (per the promises spec) then() will return a new promise. That's the first issue I see with your code.
Next is that you have to store thing1 in a higher scope so that you can access it later. So something like this might be in order:
// example
var thing1;
getThing1()
.then(function(value){
thing1 = value; // <-- store in higher scope
return getThing2(); // <-- return
})
.then(function(value){
thing2 = value;
// now you have both thing1 and thing2 in scope
})
I'd like to summarize what I've learned from the two helpful answers from #greim and #c-smile. +1 both for the kind help. But If I'm understanding correctly, then this is a disappointment for me about Promises.
#greim's answer will make my callbacks refer to local variables in the containing function. But this is disappointing because it makes the callback dependent those locals. It would be hard, for example, to pass in a callback.
#c-smile's answer will make my functions return an array (or some collection) of their parameters and their result. All callers of that function would then be expected to dig through the return value for the "natural" result as opposed to the params that it used to get the result.
Restating the answers in terms of my original post:
// #greim
function doAsnychThingsInSequence(params) {
var theResultOfThing0; // yuck, for needing to make up a non-colliding variable name
return doThing0(params).then(function(resultOfThing0) {
theResultOfThing0 = resultOfThing0;
return doThing1(resultOfThing0);
}).then(function(resultOfThing1) {
return doThing2(theResultOfThing0, resultOfThing1);
}, function(error) {
// handle error
});
}
// #c-smile
function doThing1(params) { // params are resultOfThing0
// do stuff
// the "natural" answer is just resultOfThing1, but ...
return [params, resultOfThing1];
}
// if this function was doThingN, then we'd have
return [params0, params1 ... paramsN, resultOfThingN]; // yikes!
// at least this method looks great now, how I want it to look...
function doAsnychThingsInSequence(params) {
return doThing0(params).then(function(resultOfThing0) {
return doThing1(resultOfThing0);
}).then(function(resultOfThing0, resultOfThing1) {
return doThing2(resultOfThing0, resultOfThing1);
}, function(error) {
// handle error
});
}