I have written a library function pRead(Path), which returns a JavaScript Promise to read a file on the local computer under an Apache server, using Ajax. I won't include the code for this, as it uses standard technology that is well-known to anyone who can give a good answer to this question.
I want to write a second library function, pReadObj(Path), which will return a Promise to read a JSON file and provide its object value to asynchronous code. It should work like this:
pReadObj("test.json").then(then2).catch(pErr);
function then2(obj)
{
alert(JSON.stringify(obj)); // Shows the JSON obj
} // then2
Here is the code I wrote:
var globalPreviousResolve;
function pReadObj(Path) // Promise to read JSON from file
{
return new Promise(function(resolve,reject)
{
globalPreviousResolve=resolve;
pRead(Path).then(pReadObj2).catch(pErr);
});
} // pReadObj
function pReadObj2(JSONStr)
{
globalPreviousResolve(JSON.parse(JSONStr));
} // pReadObj2
function pTestDB() // Called from button
{
pReadObj("test.json").then(then2).catch(pErr);
} // pTestDB
This works, but has a problem: using a global variable to hold the resolve callback is not only ugly, but it will clearly malfunction if two calls to pReadObj happen within a short period of time and the disk read takes a longer time than that.
The resolve function needs to be stored inside the Promise in some way, I'm thinking.
There's no need to explicitly create a Promise; just return the one created by .then:
function pReadObj(Path) {
return pRead(Path).then(JSON.parse);
}
Related
This problem should be clear: I want to write into a log file using a statement like the following. You can assume that this statement is inside a click handler for a button. There are two questions embedded in this pseudocode.
pLogInfo("local info").then(pauseUntilSettled).catch(err); // This is my goal
Here are my library functions (each returns a Promise):
// Get info asynchronously from a server (simple, should work)
function pGetServerInfo()
{
// then value: "server info"
} // pGetServerInfo
// Write into a file asynchronously (simple, should work)
function pWriteFile(path,string)
{
// no then value
} // pWriteFile
// Write formatted info into a log file asynchronously
function pLogInfo(localInfo)
{
pGetServerInfo().then(p2);
} // pLogInfo
function p2(serverInfo)
{
// Should write "local info, server info"
// But where is the local info? It got lost.
return pWriteFile('log',localInfo+','+serverInfo);
} // p2
Usage:
pLogInfo("local info").then(pauseUntilSettled).catch(err);
function pauseUntilSettled()
{
// How to wait before returning from
// the button click event handler?
} // pauseUntilSettled
ADDED 8/27/19:
Several possible solutions to this common problem occur to me:
Attach an object to one of the top functions in your chain (p.data={}). You can store any arguments or callbacks you wish in the object and reference them in any asynchronous 'then' code. This works because the function that is the parent of the object has global scope. It can fail if you fire another top-level Promise of the same thread while an existing Promise is not yet settled, because the new execution thread will share and overwrite the object. I have successfully used this approach, a variant of the above global formulation, but it is clearly unsafe.
Create a closure function to propagate the asynchronous thread. A closure function contains a snapshot of its arguments and referenced global variables. I have not yet gotten this idea to work, but it seems reasonable.
Create a new Promise, either as part of the thread of Promises or as a separate helper, that calls its resolve function with an object instead of a single value. Use that object to propagate more than one value to each "then" function. I have not gotten this idea to work either.
I hope these ideas inspires someone (including myself) to come up with a solution, because it is a very common problem that is not often discussed.
Satpal gave a great answer for promises. Another option is to use the RXJS library and utilize observables which are built on top of Promises. They have a next(), error() and complete() block where code will only execute once values are received in the stream. If you want to wait on multiple services to respond you can combine the streams together in multiple ways.
I know this is not the exact answer you are looking for but it is a very useful library. Here is the documentation. https://rxjs-dev.firebaseapp.com/api/index/class/Observable
Solution:
You can put intermediate values in scope in any later 'then' function explicitly, by using 'bind'. It is a nice solution that doesn't require changing how Promises work, and only requires a line or two of code to propagate the values just like errors are already propagated.
Here is a complete example:
// Get info asynchronously from a server
function pGetServerInfo()
{
// then value: "server info"
} // pGetServerInfo
// Write into a file asynchronously
function pWriteFile(path,string)
{
// no then value
} // pWriteFile
// The heart of the solution: Write formatted info into a log file asynchronously,
// using the pGetServerInfo and pWriteFile operations
function pLogInfo(localInfo)
{
var scope={localInfo:localInfo}; // Create an explicit scope object
var thenFunc=p2.bind(scope); // Create a temporary function with this scope
return (pGetServerInfo().then(thenFunc)); // Do the next 'then' in the chain
} // pLogInfo
// Scope of this 'then' function is {localInfo:localInfo}
function p2(serverInfo)
{
// Do the final 'then' in the chain: Writes "local info, server info"
return pWriteFile('log',this.localInfo+','+serverInfo);
} // p2
This solution can be invoked as follows:
pLogInfo("local info").then().catch(err);
(Note: a more complex and complete version of this solution has been tested, but not this example version, so it could have a bug as written.)
I'm having trouble understanding how Node operates regarding it's parallel processing and returning values from function calls.
FYI: The gulp function below is merely created as an example for this question.
Is it possible that the function could return the stream before the Read a large file statement has finished processing (the large file has been fully read from the file system and the stream has been added), or is Node smart enough to complete all statements before returning?
function moveFiles(){
var gulp = require('gulp'),
stream = require('merge-stream')();
// Read a large file
stream.add(gulp.src('src/large-file.txt')
.pipe(gulp.dest('dest/'))
);
// Read a small file
stream.add(gulp.src('src/small-file.txt')
.pipe(gulp.dest('dest/'))
);
return (stream.isEmpty() ? null : stream);
}
Could Node feasibly return a value from a function call before completing all operations within the function itself?
This is a tricky question. The answer is no, in a way that returning a value means that the function is finished executing, it's taken back from the stack and it will never do anything again - unless it's invoked another time of course, but the point is that this particular invocation is over.
But the tricky part is that it's the function that's finished executing and it doesn't mean that it couldn't schedule something else to happen in the future. It will get more complicated in a minute but first a very simple example.
function x() {
setTimeout(function () {
console.log('x1'));
}, 2000);
console.log('x2');
return;
console.log('x3');
}
Here when you call x() then it will schedule another function to run after 2 seconds, then it will print x2 and then it will return - at which point this function cannot do anything else ever again for that invocation.
It means that x3 will never get printed, but x1 will eventually get printed - because it's another function that will be called when the timeout fires. The anonymous function will get called not because the x() function can do anything after it returns, but because it managed to schedule the timeout before it returned.
Now, instead of just scheduling things to happen in the future, a function can return a promise that will get resolved some time later. For example:
function y() {
console.log('y1');
return new Promise(function (resolve, reject) {
setTimeout(function () {
resolve('message from y()');
}, 2000);
});
console.log('y2');
}
Now, when you run:
var promise = y();
what will happen is that y1 will get printed, a new promise will get returned and y2 will never get printed because at that point y() returned and cannot do anything else. But it managed to schedule a timeout that will resolve the promise after two seconds.
You can observe it with:
promise.then(function (value) {
console.log(value);
});
So with this example you can see that while the y() function itself returned and cannot do anything else, some other (anonymous in this case) function can be called in the future and finish the job that the y() function has initiated.
So I hope now it's clear why it's a tricky question. In a way a function cannot do anything after returning. But it could have scheduled some other functions as timeouts, event handlers etc. that can do something after the functions returns. And if the thing that the function returns is a promise then the caller can easily observe the value in the future when it's ready.
All of the examples could be simplified by using the arrow functions but I wanted to make it explicit that those are all separate functions, some of them are named, some are anonymous.
For more details see some of those answers:
A detailed explanation on how to use callbacks and promises
Explanation on how to use promises in complex request handlers
An explanation of what a promise really is, on the example of AJAX requests
An explanation of callbacks, promises and how to access data returned asynchronously
I want to implement a dynamic loading of a static resource in AngularJS using Promises. The problem: I have couple components on page which might (or not, depends which are displayed, thus dynamic) need to get a static resource from the server. Once loaded, it can be cached for the whole application life.
I have implemented this mechanism, but I'm new to Angular and Promises, and I want to make sure if this is a right solution \ approach.
var data = null;
var deferredLoadData = null;
function loadDataPromise() {
if (deferredLoadData !== null)
return deferredLoadData.promise;
deferredLoadData = $q.defer();
$http.get("data.json").then(function (res) {
data = res.data;
return deferredLoadData.resolve();
}, function (res) {
return deferredLoadData.reject();
});
return deferredLoadData.promise;
}
So, only one request is made, and all next calls to loadDataPromise() get back the first made promise. It seems to work for request that in the progress or one that already finished some time ago.
But is it a good solution to cache Promises?
Is this the right approach?
Yes. The use of memoisation on functions that return promises a common technique to avoid the repeated execution of asynchronous (and usually expensive) tasks. The promise makes the caching easy because one does not need to distinguish between ongoing and finished operations, they're both represented as (the same) promise for the result value.
Is this the right solution?
No. That global data variable and the resolution with undefined is not how promises are intended to work. Instead, fulfill the promise with the result data! It also makes coding a lot easier:
var dataPromise = null;
function getData() {
if (dataPromise == null)
dataPromise = $http.get("data.json").then(function (res) {
return res.data;
});
return dataPromise;
}
Then, instead of loadDataPromise().then(function() { /* use global */ data }) it is simply getData().then(function(data) { … }).
To further improve the pattern, you might want to hide dataPromise in a closure scope, and notice that you will need a lookup for different promises when getData takes a parameter (like the url).
For this task I created service called defer-cache-service which removes all this boiler plate code. It writted in Typescript, but you can grab compiled js file. Github source code.
Example:
function loadCached() {
return deferCacheService.getDeferred('cacke.key1', function () {
return $http.get("data.json");
});
}
and consume
loadCached().then(function(data) {
//...
});
One important thing to notice that if let's say two or more parts calling the the same loadDataPromise and at the same time, you must add this check
if (defer && defer.promise.$$state.status === 0) {
return defer.promise;
}
otherwise you will be doing duplicate calls to backend.
This design design pattern will cache whatever is returned the first time it runs , and return the cached thing every time it's called again.
const asyncTask = (cache => {
return function(){
// when called first time, put the promise in the "cache" variable
if( !cache ){
cache = new Promise(function(resolve, reject){
setTimeout(() => {
resolve('foo');
}, 2000);
});
}
return cache;
}
})();
asyncTask().then(console.log);
asyncTask().then(console.log);
Explanation:
Simply wrap your function with another self-invoking function which returns a function (your original async function), and the purpose of wrapper function is to provide encapsulating scope for a local variable cache, so that local variable is only accessible within the returned function of the wrapper function and has the exact same value every time asyncTask is called (other than the very first time)
I want get a image from my local harddrive. My function returns undefined.
function getImageFromLocal() {
var imageurl = 0;
return new WinJS.Promise(function () {
picturesLib.getItemAsync("APP Folder X").then(function (appfolder) {
appfolder.getItemAsync("Subfolder X").then(function (file) {
file.getItemAsync("Image.jpg").done(function (image) {
imageurl = URL.createObjectURL(image);
});
});
});
});
return imageurl;
}
When working with asynchronous API calls like StorageFolder.getItemAsync, it doesn't work to assign the ultimate result to a local variable and attempt to return that.
In your code, moreover, you have two return statements, the first of which returns a promise inside which is wrapped the return value from a chain of promises ending in .done, which is what's giving you undefined. The second return statement is never reached, and even if it were, would return the initial value of 0 because by that time the async code won't have executed.
I'll point out further that using new WinJS.Promise isn't at all what you want here, but I won't belabor the details. If you really want the full story on promises and how they work, refer to my free ebook, Programming Windows Store Apps with HTML, CSS, and JavaScript, Second Edition, both in Chapter 3 (basics) and Appendix A (full story).
Back to your code, I'm not entirely sure what you're trying to do. With your function named with "Local" you suggest you're trying to retrieve an image from your app data, but the use of a variable called picturesLib suggests you're coming from the Pictures library. Let me address each of those separately.
First, if you're working with local appdata, you can bypass the entire process you've shown here by simply using a URI in the form ms-appdata:///local/folder1/folder2/image.jpg. You can assign such a URI directly to an img.src attribute and it'll just work. This will work synchronously, and also works for ms-appx:/// URIs that refer to in-package resources.
Second, when working with images you cannot reference so conveniently with a URI, you need to get its StorageFile and pass that to URL.createObjectURL, the result of which you can assign to an img.src as well, as I think you know. To save some trouble, though, you can use a full relative path with StorageFile.getFileAsync. This way you avoid having to write a promise chain to navigate a folder structure and reduce all that to a single call. That is, if pictureLib is the StorageFolder from Windows.Storage.KnownFolders.picturesLibrary, then you can just use:
picturesLib.getFileAsync("App folder X\subfolder x\image.jpg");
Now for your original function, whose purpose is to return a URI for that image, it's necessary that it returns a promise itself, and that the caller attached a .done to that promise to obtain the result. You do this properly by returning the value you want inside the completed handler for getFileAsync, but be sure to use .then which returns a promise for that return value (.done will return undefined).
function getPicturesLibUriAsync(relativePath) {
return picturesLib.getFileAsync(relativePath).then(function (file) {
return URL.createObjectURL(file);
});
}
Then you call the method like this, using .done to get the async result:
getPicturesLibUriAsync("folder1\folder2\image1.jpg").done(function (uri) {
someImageElement.src = uri;
});
Again, refer to my book for full details on how promises work, especially using the return value of .then.
I want to implement a dynamic loading of a static resource in AngularJS using Promises. The problem: I have couple components on page which might (or not, depends which are displayed, thus dynamic) need to get a static resource from the server. Once loaded, it can be cached for the whole application life.
I have implemented this mechanism, but I'm new to Angular and Promises, and I want to make sure if this is a right solution \ approach.
var data = null;
var deferredLoadData = null;
function loadDataPromise() {
if (deferredLoadData !== null)
return deferredLoadData.promise;
deferredLoadData = $q.defer();
$http.get("data.json").then(function (res) {
data = res.data;
return deferredLoadData.resolve();
}, function (res) {
return deferredLoadData.reject();
});
return deferredLoadData.promise;
}
So, only one request is made, and all next calls to loadDataPromise() get back the first made promise. It seems to work for request that in the progress or one that already finished some time ago.
But is it a good solution to cache Promises?
Is this the right approach?
Yes. The use of memoisation on functions that return promises a common technique to avoid the repeated execution of asynchronous (and usually expensive) tasks. The promise makes the caching easy because one does not need to distinguish between ongoing and finished operations, they're both represented as (the same) promise for the result value.
Is this the right solution?
No. That global data variable and the resolution with undefined is not how promises are intended to work. Instead, fulfill the promise with the result data! It also makes coding a lot easier:
var dataPromise = null;
function getData() {
if (dataPromise == null)
dataPromise = $http.get("data.json").then(function (res) {
return res.data;
});
return dataPromise;
}
Then, instead of loadDataPromise().then(function() { /* use global */ data }) it is simply getData().then(function(data) { … }).
To further improve the pattern, you might want to hide dataPromise in a closure scope, and notice that you will need a lookup for different promises when getData takes a parameter (like the url).
For this task I created service called defer-cache-service which removes all this boiler plate code. It writted in Typescript, but you can grab compiled js file. Github source code.
Example:
function loadCached() {
return deferCacheService.getDeferred('cacke.key1', function () {
return $http.get("data.json");
});
}
and consume
loadCached().then(function(data) {
//...
});
One important thing to notice that if let's say two or more parts calling the the same loadDataPromise and at the same time, you must add this check
if (defer && defer.promise.$$state.status === 0) {
return defer.promise;
}
otherwise you will be doing duplicate calls to backend.
This design design pattern will cache whatever is returned the first time it runs , and return the cached thing every time it's called again.
const asyncTask = (cache => {
return function(){
// when called first time, put the promise in the "cache" variable
if( !cache ){
cache = new Promise(function(resolve, reject){
setTimeout(() => {
resolve('foo');
}, 2000);
});
}
return cache;
}
})();
asyncTask().then(console.log);
asyncTask().then(console.log);
Explanation:
Simply wrap your function with another self-invoking function which returns a function (your original async function), and the purpose of wrapper function is to provide encapsulating scope for a local variable cache, so that local variable is only accessible within the returned function of the wrapper function and has the exact same value every time asyncTask is called (other than the very first time)