My program downloads a large amount of data, processes it, and makes it available through a returned function. The program gets ahread of the download, so I am adding promises to make it wait for the data to arrive.
function dataSource(...) {
var _data = null;
// download: a promise that returns data for the _data object.
let download = function() { ... }
return function(...) {
if (!_data) {
download(...).then(data => _data = data).done();
}
var datum = _data[key];
var outbound = doSomethingWithData(datum);
return outbound;
}
}
My code is structured like this because the function that Engine returns makes my code very neat.
var generate = dataSource(param1,param2);
var fullName = generate("malename")+" "+generate("malename")+" "+generate("surname");
The specific requirements are:
Download the data only once.
Query the data by key any number of times without downloading the data again.
Do not change the existing interface.
I could have dataSource return a promise rather than a function. I know what the pattern for using promises looks like. But that will force me to rewrite the code that consumes this function. This pattern is used extensively throughout the code, and changing it isn't an acceptable solution.
How can I structure this to ensure that my function doesn't return until it has the data, without returning the promise?
This should fix it
function dataSource(){
return function(){
return download(...).then(data=>doSomethingWithData(data[key]));
};
}
var generate = dataSource();
Promise
.all(["malename","malename","surname"].map((name)=>return generate(name)))
.spread((name1,name2,name3)=>{
return [name1,name2,name2].join(" ");
});
the spread is not needed, but it helps for illustration purposes
Have dataSource return a promise rather than the data function. The revised dataSource looks like this:
function dataSource(...) {
var _data = null;
// download: a promise that returns data for the _data object.
let download = function() { ... }
function _generate(...) {...}
return download(group,subgroup,options).then(data => _data = data).then(() => _generate);
}
Then, where the code is consumed, get the generate function from the returned promise:
let generate = function() {};
dataSource.then(fn => generate = fn).done();
Related
Final? Update: Solution 1: I changed func1 to NOT return a value back to my javascript event, but instead to return the value directly to my calling method using
return DotNet.invokeMethodAsync('AppWASM', 'SetImageData', result);
from func1 (so I also changed invoke of func1(file) to not assign to a variable). However, the limitation that the method had to be static had me going in circles. I could have used this to save it directly to my database but from the app's standpoint that wasn't a good solution.
Final Solution: I saved the value to local storage via
localStorage.setItem("thisimage", result);
which is in my revised code, below. For this app, that makes the most sense since the idea is to hold the image to see if the user wants to save it (or not). So I think I kind of got around the Promise confusion by just going a different route - which is what I found in the similar questions. The code (and image below) shows the value stored in localstorage.
=================================================================**
1st Update: I revised my code to return the Promise eventArg as a JsonDocument (property set as public object Promise {get; set;} in my eventargs class). How do I extract the data from this Promise in C#? The json document returned does not have a Promise.result element.
I know there are similar questions - scripts can log the result of a Promise to the console, but the returned result is undefined - but the answers are to call the promise function from an async function (and many answers have comments that they don't work). Since my "calling" script is for an event, I don't see how to make it async. So event execution continues without waiting for the result. I've tried assigning to a top level variable with same result. The custom event is fired by a browser event, and the args are returned to a method in the razor page. I'd like to send imageB64string (or globalimagevar) as a return arg. This is for Net6 Blazor WASM (and Server once it works) apps.
async function func1(fileParam) {
await blobToBase64(fileParam).then(result => {
localStorage.setItem("thisimage", result);
return;
});
}
async function blobToBase64(blob) {
const reader = new FileReader();
reader.readAsDataURL(blob);
return await new Promise((resolve, _ ) => {
reader.onloadend = () => {
resolve(reader.result);};});}
Blazor.registerCustomEventType("pastemultimedia", {
browserEventName: 'paste', createEventArgs: event => {
let isMultimedia = false;
let data = event.clipboardData.getData('text');
let promise = "";
const items = event.clipboardData.items;
const acceptedMediaTypes = ["image/png"];
for (let i = 0; i < items.length; i++) {
const file = items[i].getAsFile();
if (!file) { continue; }
if (!items[i].type.includes('image')) { continue; }
if (acceptedMediaTypes.indexOf(items[i].type) === -1) { continue; }
isMultimedia = true;
const url = window.URL || window.webkitURL;
data = url.createObjectURL(file);
func1(file);
}
return { isMultimedia, data };
}
})
Promise result as json (partial shown):
Oriignal Result / execution continued before promise was resolved:
imageB64string is: function toString() { [native code] }
ImagePasteEvent.js:40 globalimagevar is:
ImagePasteEvent.js:5 logged from func1: data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAVEAAAHaCAYAAAC5AXpZAAAgAElEQVR4nO3df3xU9YHv/xfJYZiEiTNhJmbCBJliiMFgDBo2CroGBbGyUlqxtqDfsqU+9F573W6rbRd7v9r7uHXd1nZdv2VXH1y37u1qa6tbyi60FCpxKWiWKJESDRBoEEISMyETMwzDMAnfP2byexICn0B....
Note: Credit for the base code for the pastemultimedia event to Felipe Gavilan blog
i am trying to use Promise to make something easy, but it appear to be a real nightmare with promises. I think i am missing something with it.
I would Like to :
Fetch some articles in database
Look into each article found and iterate over article.authors Array.
Fetch each author in dataBase (including author.images) for each article
Send back to client the articles List from step One but updated with article.authors.images
I tryed severals ways using Map / Each / spread / Reduce / _.clone / _cloneDeep
But nothing works as expected
Any help would be appreciate
return Promise.bind({})
.then(function find_article(){
return Article.find().sort(req.params.sort).skip(req.params.page).limit(req.params.limit).populateAll()
}).then(function(articles){
dataBack = articles
var articlesPromise = Promise.map(articles,function(article){
console.log('------------');
var AuthorsPromise = Promise.map(article.authors,function(author){
return User.findOne(author.id).populateAll().then(function(){
})
})
return Promise.all(AuthorsPromise).then(function(data){
console.log('*******************************');
console.log(data);
return data
})
})
return Promise.all(articlesPromise).then(function(allArticles){
console.log('++++++++++++++++++++++++++++++');
console.log(allArticles);
})
})
.then(function(WhatIsInThere){
console.log('somethinAfter');
console.log(WhatIsInThere);
})
I got Something like this, but is still doesnt work i am still missing the point of the .all()
This is not trivial task, you need to chain promises and most probably use function like Promise.all() or jQuery.when()
Your code should look like this
// function that fetch from database and returns promise
function getArticleFromDatabase(articleId) {
return new Promise();
}
// function that fetch from database and returns promise
function getAuthorFromDatabase(authorId) {
return new Promise();
}
var articleIds = [1, 2, 3]; // lets have some array with article ids
// then turn it into array of promises
var articlePromises = articleIds.map(function(articleId) {
var articlePromise = getArticleFromDatabase(articleId);
// return the promise
return articlePromise.then(function(articleData) {
// here we have available complete article data
var articleAuthors = articleData.authors; // supose it's array of author ids
// lets turn it into author data promises
var authorsPromises = articleAuthors.map(function(author) {
return getAuthorFromDatabase(author.id);
});
// return new Promise, so our first promise of article data
// will return promise of article data with authors data
return Promise.all(authorsPromises)
.then(function(fullfilledAuthorsData) {
// fill in authors data
articleData.authors = fullfilledAuthorsData;
// return complete article data with authors data
return articleData;
});
});
});
Promise.all(articlePromises).then(function(fullfilledArticleData) {
// here you have available complete article data from all articles
// fullfilledActicledata is array mapped from initial array of ids
// so fullfilledActicleData[0] has data for articleIds[0],
// fullfilledActicleData[1] has data for articleIds[1] etc.
// You can use the fullfilledArticleData freely.
});
Based on your code
// this method obviously returns Promise already
var articlesPromise = Article
.find()
.sort(req.params.sort)
.skip(req.params.page)
.limit(req.params.limit)
.populateAll();
// attach callback via .then()
articlesPromise
.then(function(articles) {
// here we have fullfilled articles data already
var articlesWithAuthorsPromises = articles.map(function(article) {
var authorsPromises = article.authors.map(function(author) {
return User.findOne(author.id).populateAll();
});
return Promise.all(authorsPromises)
.then(function(fullfilledAuthors) {
article.authors = fullfilledAuthors;
return article;
})
})
// return new Promise
return Promise.all(articlesWithAuthorsPromises)
})
// attach another callback via .then()
.then(function(fullData) {
console.log(fullData);
})
// you should also listen for errors
.catch(errorCallback)
This is my first stab at attempting to put together a node module and I am still trying to wrap my head around how to structure the asynchronous callbacks. This is a case in point. Right now I am trying to use featureService.getCount() and getting nothing in response. Using breakpoints, I know featureService.getUniqueIds() is working.
Since a callback is in there, I am assuming the reason why I am not getting a length back is the callback in getCount has not responded yet. After looking at this for most of the afternoon and not really coming up with a very good solution other than a recursive loop checking for the value to be populated with a timeout, I am asking for advice how to better structure my code to accomplish the task at hand.
I have read a bit about promises. Is this an applicable instance or even a viable solution? I really have no clue how to implement promises, but it makes logical sense in such an instance.
Obviously I am lost here. Thank you for any help you can offer.
var Client = require('node-rest-client').Client;
var client = new Client();
var featureService = function(endpoint){
var uniqueIds;
var count;
// get list of unique id's
this.getUniqueIds = function(){
if (!uniqueIds) {
var options = {
parameters: {
f: 'json',
where: "OBJECTID LIKE '%'",
returnIdsOnly: 'true'
}
};
client.get(endpoint + '/query', options, function(data, res){
var dataObject = JSON.parse(data);
var uniqueIds = dataObject.objectIds;
return uniqueIds;
});
} else {
return uniqueIds;
}
};
// get feature count
this.getCount = function(){
// get uniqueIds
uniqueIds = this.getUniqueIds();
// get length of uniqueIds
count = uniqueIds.length;
};
// get list of unique attribute values in a single field for typeahead
this.getTypeaheadJson = function(field){};
// find features in a field with a specific value
this.find = function(field, value){};
};
var endpoint = 'http://services.arcgis.com/SgB3dZDkkUxpEHxu/arcgis/rest/services/aw_accesses_20140712b/FeatureServer/1';
var afs = new featureService(endpoint);
console.log(afs.getCount());
exports.featureService = featureService;
Now, after working it over some more and using request as in the bluebird documentation (I could not get the above module to work), I have this working, but cannot figure out how to get the calculated value to work with, the number of iterations.
var Promise = require("bluebird"),
request = Promise.promisifyAll(require("request"));
var FeatureService = function(){
// get count from rest endpoint
var getCount = function(){
var optionsCount = {
url: endpoint + '/query',
qs: {
f: 'json',
where: "OBJECTID LIKE '%'",
returnCountOnly: 'true'
}
};
return request.getAsync(optionsCount)
.get(1)
.then(JSON.parse)
.get('count');
};
// get max record count for each call to rest endpoint
var getMaxRecordCount = function(){
var optionsCount = {
url: endpoint,
qs: {
f: 'json'
}
};
return request.getAsync(optionsCount)
.get(1)
.then(JSON.parse)
.get('maxRecordCount');
};
// divide the total record count by the number of records returned per query to get the number of query iterations
this.getQueryIterations = function(){
getCount().then(function(count){
getMaxRecordCount().then(function(maxCount){
return Math.ceil(count/maxCount);
});
});
};
};
// url to test against
var endpoint = 'http://services.arcgis.com/SgB3dZDkkUxpEHxu/arcgis/rest/services/aw_accesses_20140712b/FeatureServer/1';
// create new feature service object instance
afs = new FeatureService();
// This seems like it should work, but only returns undefined
console.log(afs.getQueryIterations());
// this throws an error telling me "TypeError: Cannot call method 'then' of undefined"
//afs.getQueryIterations().then(function(iterCount){
// console.log(iterCount);
//});
Yes, use promises! They're a powerful tool, made for exactly this purpose, and with a decent library they're easy to use. In your case:
var Promise = require('bluebird'); // for example, the Bluebird libary
var Client = Promise.promisifyAll(require('node-rest-client').Client);
var client = new Client();
function FeatureService(endpoint) {
var uniqueIds;
var count;
// get list of unique id's
this.getUniqueIds = function(){
if (!uniqueIds) { // by caching the promise itself, you won't do multiple requests
// even if the method is called again before the first returns
uniqueIds = client.getAsync(endpoint + '/query', {
parameters: {
f: 'json',
where: "OBJECTID LIKE '%'",
returnIdsOnly: 'true'
}
})
.then(JSON.parse)
.get("objectIds");
}
return uniqueIds;
};
// get feature count
this.getCount = function(){
if (!count)
count = this.getUniqueIds() // returns a promise now!
.get("length");
return count; // return a promise for the length
};
// get list of unique attribute values in a single field for typeahead
this.getTypeaheadJson = function(field){};
// find features in a field with a specific value
this.find = function(field, value){};
};
var endpoint = 'http://services.arcgis.com/SgB3dZDkkUxpEHxu/arcgis/rest/services/aw_accesses_20140712b/FeatureServer/1';
var afs = new FeatureService(endpoint);
afs.getCount().then(function(count) {
console.log(count);
}); // you will need to use a callback to do something with async results (always!)
exports.FeatureService = FeatureService;
Here, using Bluebird's Promise.promisifyAll, you can just use .getAsync() instead of .get(), and will get a promise for the result.
// divide the total record count by the number of records returned per query to get the number of query iterations
this.getQueryIterations = function(){
getCount().then(function(count){
getMaxRecordCount().then(function(maxCount){
return Math.ceil(count/maxCount);
});
});
};
That's the right idea! Only you always want to return something from .then handlers, so that the promise returned by the .then() call will resolve with that value.
// divide the total record count by the number of records returned per query to get the number of query iterations
this.getQueryIterations = function(){
return getCount().then(function(count){
// ^^^^^^ return the promise from the `getQueryIterations` method
return getMaxRecordCount().then(function(maxCount){
// ^^^^^^ return the promise for the iteration number
return Math.ceil(count/maxCount);
});
});
};
Now, you get back a promise for the innermost result, and this will work now:
afs.getQueryIterations().then(function(iterCount){
console.log(iterCount);
});
I have done a lot of reading around this, but ultimately the tutorials and guides I have found differ too much for me to get a decent grasp on this concept.
This is what I want to achieve:
1) Simple http request from our server [Any API for demonstration]
2) Run a function with data from (1). [Remove a property from the object]
3) Use result and length of (2) to run a loop of $http requests to our server. [Or any server]
4) This will result in 6 different objects. Run a function on these 6 objects. [Add a property]
5) Once ALL of this is done, run a separate function [Log "finished"]
How can this be achieved using promises? How do I pass data from (1) via a promise to (2)? Is this the right way to achieve what I need to do?
If anyone can show me how this should be structured it would be immensely helpful; I have kept the functions as simple as possible for this question.
Yes, promises are very nice to structure solutions for this kind of problems.
Simplified solution (more or less pseudo-code):
$http(...)
.then(function(response) {
// do something with response, for example:
var list = reponse.data.list;
// return it so that you can use it in the next 'then'.
return list;
})
.then(function(list) {
var promises = [];
angular.forEach(list, function(item) {
// perform a request for each item
var promise = $http(...).then(function(itemResponse) {
itemResponse.extraProperty = true;
return itemResponse;
});
// we make an array of promises
promises.push(promise);
});
// combine all promises into one and return it for the next then()
return $q.all(promises);
})
.then(function(itemsList) {
// itemsList is now an array of all parsed item responses.
console.log(itemsList);
});
(Hopefully this is right, I did not tested it.)
As you can see, you can return values in a callback to pass it to the next then(), or you can pass a promise, and this will result in calling the next callback when it resolves. $q.all() is used to combine multiple promises into one and resolve if all are resolved.
Edit: I realised that you can optionally leave out these three lines:
return list;
})
.then(function(list) {
But it is nice syntax though, because the separation of tasks is more visible.
Check code below, it could contains syntax error, the important is the structure. Step3 contains multiple(6) $http requests, it waits until the last request response to return a unique response object (array) containing response for each $http requets.
//Step 1
var Step1 = function () {
$http.get('api/controller').success(function (resp) {
var object1 = resp;
Step2(object1);
Step3(object1).then(function (resp) {
//resp.data is an array containing the response of each $http request
Step4(resp);
Step5();
});
});
}
//Step2
var Step2 = function(obj){
//do whatever with the object
}
//Step3
var Step3 = function (object1) {
var call = $q.defer();
var get1 = $http.get(object1[0].url);
var get2 = $http.get(object[1].url2);
//...
var get6 = $http.get(object[5].url6);
$q.all([get1, get2,..get6]).then(function (resp) {
call.resolve(resp);
});
return call.promise;
}
//Step4
var Step4 = function (resp) {
for (var i=0; i<resp.data.lenght;i++){
DoWhatEver(resp.data[i]);
};
}
//Step5
var Step5 = function () {
alert("Finished");
}
Step1(); //Call Step1 function
Don't know why you have difficulty implementing this, but maybe $q.all() is what you're missing:
var config1={method:'GET',url:'/api/...'};
$http(config1).success(function(resultsFrom1){
functionForResultsOf1(resultsFrom1);
})
var functionForResultsOf1 = function(resultsOf1){
//remove something from the result, assuming this is a synchronous operation
resultsOf1.splice()...;
var promises=makePromises(*pass whatever you want*);
$q.all(promises).then(function(aggregateOfAllCallsToServer){
angular.forEach(aggregateOfAllCallsToServer,function(data){
//do something to data from each call to the server
})
console.log("finished");
})
}
var makePromises = function(serverUrls){
var promises = [];
angular.forEach(serverUrls, function(url) {
var promise=$http({
url : '/api/'+url,
method: 'GET',
})
promises.push(promise);
});
return $q.all(promises);
}
in my project I'm not using callbacks instead I'm trying to use $.Deferred to have uniform logic across all application, I have lots places in my code where I do something like the following:
function someMagicHandler(request) {
var me = this;
var sandbox = me.getSandbox();
var options = request.options;
var deferred = request.deferred;
var result = [];
var databaseOperation = sandbox.database.all('records').done(function (records) {
result.concat(records);
deferred.notify(records);
});
var serverResponse;
var serverOperation = sandbox.server.requestRecords(options).then(function (response) {
// First we are trying to save received records to database
serverResponse = response;
result.concat(response.Records);
deferred.notify(response.Records);
return sandbox.database.put('records', response.Records);
}).done(function() {
sandbox.storage.setTimestamp('records', new Date(serverResponse.Timestamp));
});
$.when(databaseOperation, serverOperation).then(deferred.resolve, deferred.reject);
}
In this code I'm personally don't like one of the last lines:
$.when(databaseOperation, serverOperation).then(deferred.resolve, deferred.reject);
Is there a standard way to express:
$.when(databaseOperation, serverOperation).then(deferred);
which would essentially mean:
$.when(databaseOperation, serverOperation).then(deferred.resolve, deferred.reject, deferred.notify);
Edit
I've investigated this problem deeper, and it seems that below solution is ok only when you are not relying on deferred.progress() which has no memory and therefore will not return any data in case when subsequent async operation is complete synchronously.
Summary
If you are using $.Deferred() as a callback (i.e. when you rely on notify or progress functions and in that case you need pass it as an argument) than you will be obligated to use the ugly
blahblahblah.then(deferred.resolve, deferred.reject, deferred.notify)
You can just replace this
$.when(databaseOperation, serverOperation).then(deferred.resolve, deferred.reject);
with this:
request.deferred = $.when(databaseOperation, serverOperation);
And delete all references to the variable deferred because $.when already creates a promise for you with (as far as I read the manual).