I'm not a professional. I cannot get the solution with the code below. The console.log() statements are OK, the call to showNameDialogBox is OK, and the dialog box appears with the (dummy) information passed as arguments... but, the Promise doesn't work at all and, of course, the dialog box doesn't close — obviously something I didn't do correctly.
Thanks in advance for your input.
const showNameDialogBox = async (firstName, fullname, gender, country) => { // Always returns a promise
initNameDialog(firstName, fullname, gender, country)
nameManagementDialog.showModal()
await new Promise(function (resolve, reject) {
// The executor is an anonymous function that is executed automatically after the Promise is constructed
// (the two parameters are defined internally in JS, nothing to do here)
document.getElementById("nameAddButton").addEventListener("click", function () {
addNameToBase()
resolve("Added") // Calls resolve to issue "Added" as a fulfilled value for the Promise
})
document.getElementById("nameSkipButton").addEventListener("click", function () {
resolve("Skipped") // Calls resolve to issue "Skipped" as a fulfilled value for the Promise
})
}) // End of new Promise (constructor)
nameManagementDialog.close()
} // End of showNameDialogBox
console.log("Before showNameDialog");
(async function () {
await showNameDialogBox("Zorro", "Cavalier nommé Zorro", "Male", "California, United States"); // Test
let nameSelected = document.getElementById("name").value.toLowerCase();
nameSelected = nameSelected.charAt(0).toUpperCase() + nameSelected.slice(1);
console.log(nameSelected);
})()
console.log("After showNameDialog");
It is just not how you use Promises. Just kick it out and have your event listeners trigger the close() action directly:
const showNameDialogBox = (firstName, fullname, gender, country) => {
initNameDialog(firstName, fullname, gender, country)
nameManagementDialog.showModal()
document.getElementById("nameAddButton").addEventListener("click", function() {
addNameToBase()
nameManagementDialog.close()
})
document.getElementById("nameSkipButton").addEventListener("click", function() {
nameManagementDialog.close()
})
}
console.log('Before await');
showNameDialogBox("Zorro", "Don Diego", "Male", "California, United States");
console.log('After await');
(Note that close() should probably also remove the event listeners)
Though in general, your code should work too, but you need to await showNameDialogBox() if you want to wait between the console.log() statements:
console.log('Before await');
await showNameDialogBox("Zorro", "Don Diego", "Male", "California, United States");
console.log('After await');
You might want to check though if it isn't easier to change to an event-driven approach, where one event (i.e. closing the name modal) triggers another event (i.e. opening the next dialog), instead of having a fixed flow that waits for a set of fixed steps. The advantage is that you can easily go back and forth between those loosely coupled steps. Then again, if it works for you, it works.
Related
I'm using firebase cloud functions in which I define a variable commentIdSpecific. When I log it inside the function: -- console.log(comm id ${commentIdSpecific}); -- it prints its value. When I try to print it here: -- console.log(test of variables inisde of post: ${usernameWhoOwnsThePost}, uwotpi: ${commentIdSpecific}) -- it returns undefined. I've looked at three websites talking about global vars and it doesn't seem any different from what I have here.
How do I go about getting the value in the second print statement to be in the first? Thanks in advance.
var commentIdSpecific;
db.ref(`/users/${usernameWhoOwnsThePost}/posts/${usernameWhoOwnsThePostID}/comments`).once('value').then(snap => {
commentIdSpecific = snap.val();
let ids = [];
for (var id in snap.val()) {
ids.push(id);
}
let lastValueId = ids[ids.length - 1]
console.log(`last id value ${lastValueId}. UserPost: ${usernameWhoOwnsThePost}. user owner post id: ${usernameWhoOwnsThePostID}...`);
commentIdSpecific = lastValueId;
console.log(`comm id ${commentIdSpecific}`);
return commentIdSpecific;
}).catch(err => {
console.log(err);
});
var commentPoster;
db.ref(`/users/${usernameWhoOwnsThePost}/posts/${usernameWhoOwnsThePostID}/comments/${commentIdSpecific}/comment`).once('value').then(snap => {
commentPoster = snap.val();
console.log(`commentPoster: ${snap.val()}`);
console.log(`test of variables inisde of post: ${usernameWhoOwnsThePost}, uwotpi: ${commentIdSpecific}`)
return commentPoster
}).catch(err => {
console.log(err);
});
once() is asynchronous and returns immediately with a promise that indicates when the async work is complete. Likewise, then() returns immediately with a promise. The callback you pass to then() is executed some unknown amount of time later, whenever the results of the query are finished. Until that happens, your code keeps executing at the next line, which means commentIdSpecific will be undefined when it's first accessed.
You need to use a promise chain to make sure the work that depends on the results of async work is only accessed after it becomes available.
You may want to watch the videos on JavaScript promises on this page in order to better learn how they're used in Cloud Functions. It's absolutely critical to understand how they work to write effective code.
https://firebase.google.com/docs/functions/video-series/
You should make the second db.ref call in promise chain once the first promise resolved like this:
db.ref(`/users/${usernameWhoOwnsThePost}/posts/${usernameWhoOwnsThePostID}/comments`).once('value')
.then(snap => {
commentIdSpecific = snap.val();
let ids = [];
for (var id in snap.val()) {
ids.push(id);
}
let lastValueId = ids[ids.length - 1]
console.log(`last id value ${lastValueId}. UserPost: ${usernameWhoOwnsThePost}. user owner post id: ${usernameWhoOwnsThePostID}...`);
commentIdSpecific = lastValueId;
console.log(`comm id ${commentIdSpecific}`);
return commentIdSpecific;
})
.then(commentIdSpecific => {
db.ref(`/users/${usernameWhoOwnsThePost}/posts/${usernameWhoOwnsThePostID}/comments/${commentIdSpecific}/comment`).once('value').then(snap => {
commentPoster = snap.val();
console.log(`commentPoster: ${snap.val()}`);
console.log(`test of variables inisde of post: ${usernameWhoOwnsThePost}, uwotpi: ${commentIdSpecific}`)
return commentPoster
}).catch(err => {
console.log(err);
});
})
.catch(err => {
console.log(err);
});
once() is an async operation so it might possible that console.log(test of variables inside of post: ${usernameWhoOwnsThePost}, uwotpi: ${commentIdSpecific}) executed before commentIdSpecific = snap.val(); and commentIdSpecific = lastValueId;
So what you need to do is first let db.ref(/users/${usernameWhoOwnsThePost}/posts/${usernameWhoOwnsThePostID}/comments) be completed and then make call to db.ref(/users/${usernameWhoOwnsThePost}/posts/${usernameWhoOwnsThePostID}/comments/${commentIdSpecific}/comment) in next .then() in the chain.
As you are returning commentIdSpecific form first .then() So it will be available as param in second .then().
https://javascript.info/promise-chaining will help you to dig .then chaining more into deep.
I am developing a nodejs application that needs to get settings from an array(in a settings object), call a rest api based on the settings and write the response to mongodb and repeat this for the next setting in the array.
Here is a simplified version of the application
var setting //global
process(){ //top level function
for(let s of config.settings){
setting = s;
getData();
}
}
function getData(){
init()
.then(makeRequest) // constructs and makes the rest api call
.then(insert) // writes the response to the db
.catch(function(err){
// logs err
}
}
Running it, only the data for the last setting (in the array) is written to the db and this happens for each iteration. Basically the same data is written on the db for as many iterations.
The problem I can see from this is that the for loop finishes executing, before the promises return with the value.
I have seen some examples of async.for
Any suggestions on fixing this. How do you go about designing this kind of a flow?
You can bind the settings to each function call to preserve the value. looks like you'd have to refactor though as the value would be passed in as an argument though i'm not sure if your code is pseudo code or actual code.
async await would work as well but would take longer as it would pause execution at each api call.
You should return an object or array that you can use to store an internal state for your request. Please see the example for how it works.
Also never set a global variable to store your state, with your function being asynchronous the value may not be what you expect it to be.
With this approach you are passing { init } for the first promise, then { init, request } for the next so you have the response from each part of your promise chain that you can use to make further requests.
// return an object to store the state on init
const init = () =>
new Promise((res, rej) => res({
init: 'initted'
}))
// pass init and the request to the next function in the chain
const makeRequest = ({ init }) =>
new Promise((res, rej) => res({
init,
request: {
msg: 'this is the response',
id: 33
}
}))
// insert stuff from the request
// then return the data to the next query
const insert = ({ init, request }) =>
new Promise((res, rej) => res({
request,
init,
created_at: Date.now()
}))
const trace = name => x => (console.log(name, x), x)
function getData(){
return init() // return your promise so you can chain it further
.then(trace('after init'))
.then(makeRequest)
.then(trace('after request'))
.then(insert)
.then(trace('after insert'))
.catch(console.error)
}
// call you function
getData()
// since the promise is returned we can continue the chain
.then(state => console.log({ state }))
<script src="https://codepen.io/synthet1c/pen/KyQQmL.js"></script>
All of your loop will have executed by the time the callbacks are coming in. So settings will be the last value.
Instead of relying on globals, pass setting into getData, for example.
I have an api endpoint called status. Which should be used like this:
status/ohio/columbus
status/nebraska/fremont
status/ohio/columbus/police
status/nebraska/fremont/fire
The state and city paths are mandatory but the department is not.
I'd like to have a function which updates a callback with the return value. Is the following function reasonable or confusing? Should I have multiple functions instead - getStatus and getStatusForDepartment?
function getStatus(state, city, department, callback)
{
let status = "status/"+state+"/"+city
if(typeof department != "function"){
status = status+"/"+ department
}else{
callback = department
}
...
}
Your approach is totally fine and a common pattern. Another option would be to have your function return a Promise, rather than relying on a callback. This would allow you to bypass checking if the last argument is a function or callback
function getStatus(state, city, department) {
return new Promise((resolve, reject) => {
let status = "status/"+state+"/"+city
if (department) {
status += '/' + department;
}
...
if (successThingHappens) {
resolve(data);
} else {
reject(error);
}
});
}
getStatus('CA', 'San Francisco').then((data) => {
console.log('do something with', data);
}).catch((err) => {
console.error('something went wrong', error);
});
I did not understand the part regarding the "multiple functionss instead".
But about the requirement of having optional argument in the middle of the parameter list, I follow the object approach.
Instead of doing
getStatus(state, city, department, callback)
I do like this
var arg_list = {"state": state, "city": city, "department": department, "callback": callback};
getStatus(arg_list);
And within the function, assign the value from object to the varables.
This way you can omit and use any parameter.
This approach has one very good benefit. We often need to make changes to the parameter list. Sometime add new one where as sometime remove some from the parameter list and sometime we need to change the order etc. All these operations need to be done very carefully when using the parameters since this may make the function definition different to the function call. But with this approach such cases can be easily handled.
Can anyone recommend a pattern for instantly retrieving data from a function that returns a Promise?
My (simplified) example is an AJAX preloader:
loadPage("index.html").then(displayPage);
If this is downloading a large page, I want to be able to check what's happening and perhaps cancel the process with an XHR abort() at a later stage.
My loadPage function used to (before Promises) return an id that let me do this later:
var loadPageId = loadPage("index.html",displayPage);
...
doSomething(loadPageId);
cancelLoadPage(loadPageId);
In my new Promise based version, I'd imagine that cancelLoadPage() would reject() the original loadPage() Promise.
I've considered a few options all of which I don't like. Is there a generally accepted method to achieve this?
Okay, let's address your bounty note first.
[Hopefully I'll be able to grant the points to someone who says more than "Don't use promises"... ]
Sorry, but the answer here is: "Don't use promises". ES6 Promises have three possible states (to you as a user): Pending, Resolved and Rejected (names may be slightly off).
There is no way for you to see "inside" of a promise to see what has been done and what hasn't - at least not with native ES6 promises. There was some limited work (in other frameworks) done on promise notifications, but those did not make it into the ES6 specification, so it would be unwise of you to use this even if you found an implementation for it.
A promise is meant to represent an asynchronous operation at some point in the future; standalone, it isn't fit for this purpose. What you want is probably more akin to an event publisher - and even that is asynchronous, not synchronous.
There is no safe way for you to synchronously get some value out of an asynchronous call, especially not in JavaScript. One of the main reasons for this is that a good API will, if it can be asynchronous, will always be asynchronous.
Consider the following example:
const promiseValue = Promise.resolve(5)
promiseValue.then((value) => console.log(value))
console.log('test')
Now, let's assume that this promise (because we know the value ahead of time) is resolved synchronously. What do you expect to see? You'd expect to see:
> 5
> test
However, what actually happens is this:
> test
> 5
This is because even though Promise.resolve() is a synchronous call that resolves an already-resolved Promise, then() will always be asynchronous; this is one of the guarantees of the specification and it is a very good guarantee because it makes code a lot easier to reason about - just imagine what would happen if you tried to mix synchronous and asynchronous promises.
This applies to all asynchronous calls, by the way: any action in JavaScript that could potentially be asynchronous will be asynchronous. As a result, there is no way for you do any kind of synchronous introspection in any API that JavaScript provides.
That's not to say you couldn't make some kind of wrapper around a request object, like this:
function makeRequest(url) {
const requestObject = new XMLHttpRequest()
const result = {
}
result.done = new Promise((resolve, reject) => {
requestObject.onreadystatechange = function() {
..
}
})
requestObject.open(url)
requestObject.send()
return requestObject
}
But this gets very messy, very quickly, and you still need to use some kind of asynchronous callback for this to work. This all falls down when you try and use Fetch. Also note that Promise cancellation is not currently a part of the spec. See here for more info on that particular bit.
TL:DR: synchronous introspection is not possible on any asynchronous operation in JavaScript and a Promise is not the way to go if you were to even attempt it. There is no way for you to synchronously display information about a request that is on-going, for example. In other languages, attempting to do this would require either blocking or a race condition.
Well. If using angular you can make use of the timeout parameter used by the $http service if you need to cancel and ongoing HTTP request.
Example in typescript:
interface ReturnObject {
cancelPromise: ng.IPromise;
httpPromise: ng.IHttpPromise;
}
#Service("moduleName", "aService")
class AService() {
constructor(private $http: ng.IHttpService
private $q: ng.IQService) { ; }
doSomethingAsynch(): ReturnObject {
var cancelPromise = this.$q.defer();
var httpPromise = this.$http.get("/blah", { timeout: cancelPromise.promise });
return { cancelPromise: cancelPromise, httpPromise: httpPromise };
}
}
#Controller("moduleName", "aController")
class AController {
constructor(aService: AService) {
var o = aService.doSomethingAsynch();
var timeout = setTimeout(() => {
o.cancelPromise.resolve();
}, 30 * 1000);
o.httpPromise.then((response) => {
clearTimeout(timeout);
// do code
}, (errorResponse) => {
// do code
});
}
}
Since this approach already returns an object with two promises the stretch to include any synchronous operation return data in that object is not far.
If you can describe what type of data you would want to return synchronously from such a method it would help to identify a pattern. Why can it not be another method that is called prior to or during your asynchronous operation?
You can kinda do this, but AFAIK it will require hacky workarounds. Note that exporting the resolve and reject methods is generally considered a promise anti-pattern (i.e. sign you shouldn't be using promises). See the bottom for something using setTimeout that may give you what you want without workarounds.
let xhrRequest = (path, data, method, success, fail) => {
const xhr = new XMLHttpRequest();
// could alternately be structured as polymorphic fns, YMMV
switch (method) {
case 'GET':
xhr.open('GET', path);
xhr.onload = () => {
if (xhr.status < 400 && xhr.status >= 200) {
success(xhr.responseText);
return null;
} else {
fail(new Error(`Server responded with a status of ${xhr.status}`));
return null;
}
};
xhr.onerror = () => {
fail(networkError);
return null;
}
xhr.send();
return null;
}
return xhr;
case 'POST':
// etc.
return xhr;
// and so on...
};
// can work with any function that can take success and fail callbacks
class CancellablePromise {
constructor (fn, ...params) {
this.promise = new Promise((res, rej) => {
this.resolve = res;
this.reject = rej;
fn(...params, this.resolve, this.reject);
return null;
});
}
};
let p = new CancellablePromise(xhrRequest, 'index.html', null, 'GET');
p.promise.then(loadPage).catch(handleError);
// times out after 2 seconds
setTimeout(() => { p.reject(new Error('timeout')) }, 2000);
// for an alternative version that simply tells the user when things
// are taking longer than expected, NOTE this can be done with vanilla
// promises:
let timeoutHandle = setTimeout(() => {
// don't use alert for real, but you get the idea
alert('Sorry its taking so long to load the page.');
}, 2000);
p.promise.then(() => clearTimeout(timeoutHandle));
Promises are beautiful. I don't think there is any reason that you can not handle this with promises. There are three ways that i can think of.
The simplest way to handle this is within the executer. If you would like to cancel the promise (like for instance because of timeout) you just define a timeout flag in the executer and turn it on with a setTimeout(_ => timeout = true, 5000) instruction and resolve or reject only if timeout is false. ie (!timeout && resolve(res) or !timeout && reject(err)) This way your promise indefinitely remains unresolved in case of a timeout and your onfulfillment and onreject functions at the then stage never gets called.
The second is very similar to the first but instead of keeping a flag you just invoke reject at the timeout with proper error description. And handle the rest at the then or catch stage.
However if you would like to carry the id of your asych operation to the sync world then you can also do it as follows;
In this case you have to promisify the async function yourself. Lets take an example. We have an async function to return the double of a number. This is the function
function doubleAsync(data,cb){
setTimeout(_ => cb(false, data*2),1000);
}
We would like to use promises. So normally we need a promisifier function which will take our async function and return another function which when run, takes our data and returns a promise. Right..? So here is the promisifier function;
function promisify(fun){
return (data) => new Promise((resolve,reject) => fun(data, (err,res) => err ? reject(err) : resolve(res)));
}
Lets se how they work together;
function promisify(fun){
return (data) => new Promise((resolve,reject) => fun(data, (err,res) => err ? reject(err) : resolve(res)));
}
function doubleAsync(data,cb){
setTimeout(_ => cb(false, data*2),1000);
}
var doubleWithPromise = promisify(doubleAsync);
doubleWithPromise(100).then(v => console.log("The asynchronously obtained result is: " + v));
So now you see our doubleWithPromise(data) function returns a promise and we chain a then stage to it and access the returned value.
But what you need is not only a promise but also the id of your asynch function. This is very simple. Your promisified function should return an object with two properties; a promise and an id. Lets see...
This time our async function will return a result randomly in 0-5 secs. We will obtain it's result.id synchronously along with the result.promise and use this id to cancel the promise if it fails to resolve within 2.5 secs. Any figure on console log Resolves in 2501 msecs or above will result nothing to happen and the promise is practically canceled.
function promisify(fun){
return function(data){
var result = {id:null, promise:null}; // template return object
result.promise = new Promise((resolve,reject) => result.id = fun(data, (err,res) => err ? reject(err) : resolve(res)));
return result;
};
}
function doubleAsync(data,cb){
var dur = ~~(Math.random()*5000); // return the double of the data within 0-5 seconds.
console.log("Resolve in " + dur + " msecs");
return setTimeout(_ => cb(false, data*2),dur);
}
var doubleWithPromise = promisify(doubleAsync),
promiseDataSet = doubleWithPromise(100);
setTimeout(_ => clearTimeout(promiseDataSet.id),2500); // give 2.5 seconds to the promise to resolve or cancel it.
promiseDataSet.promise
.then(v => console.log("The asynchronously obtained result is: " + v));
You can use fetch(), Response.body.getReader(), where when .read() is called returns a ReadableStream having a cancel method, which returns a Promise upon cancelling read of the stream.
// 58977 bytes of text, 59175 total bytes
var url = "https://gist.githubusercontent.com/anonymous/"
+ "2250b78a2ddc80a4de817bbf414b1704/raw/"
+ "4dc10dacc26045f5c48f6d74440213584202f2d2/lorem.txt";
var n = 10000;
var clicked = false;
var button = document.querySelector("button");
button.addEventListener("click", () => {clicked = true});
fetch(url)
.then(response => response.body.getReader())
.then(reader => {
var len = 0;
reader.read().then(function processData(result) {
if (result.done) {
// do stuff when `reader` is `closed`
return reader.closed.then(function() {
return "stream complete"
});
};
if (!clicked) {
len += result.value.byteLength;
}
// cancel stream if `button` clicked or
// to bytes processed is greater than 10000
if (clicked || len > n) {
return reader.cancel().then(function() {
return "read aborted at " + len + " bytes"
})
}
console.log("len:", len, "result value:", result.value);
return reader.read().then(processData)
})
.then(function(msg) {
alert(msg)
})
.catch(function(err) {
console.log("err", err)
})
});
<button>click to abort stream</button>
The method I am currently using is as follows:
var optionalReturnsObject = {};
functionThatReturnsPromise(dataToSend, optionalReturnsObject ).then(doStuffOnAsyncComplete);
console.log("Some instant data has been returned here:", optionalReturnsObject );
For me, the advantage of this is that another member of my team can use this in a simple way:
functionThatReturnsPromise(data).then(...);
And not need to worry about the returns object. An advanced user can see from the definitions what is going on.
I have a hash that calls a function to get a value. The problem is, the function is returning the function inside rather than the values it should.
(user is defined above this hash)
My hash:
userInfo = {
id: user.id,
email: user.email,
cars: getCars(user.id),
}
Which calls this function:
getCars = (userId) ->
id = parseInt(userId)
userRef = new Firebase("https://demo-firebase.firebaseIO.com/users/#{id}/")
userRef.on('value', (snapshot) ->
if snapshot.val() == null
["toyota"]
else
snapshot.val().cars # returns an array of cars
)
When I'm in the debugger and stepping through the function, it returns on the userRef.on line rather than the correct place in the if/else statement.
Here's the compiled JS:
getCars = function(userId) {
var id, userRef;
id = parseInt(userId);
userRef = new Firebase("https://demo-firebase.firebaseIO.com/users/" + id + "/");
return userRef.on('value', function(snapshot) {
if (snapshot.val() === null) {
return ["toyota"];
} else {
return snapshot.val().cars;
}
});
};
Any ideas why this is happening? I'm sure it's something simple I'm overlooking.
So the data you are getting from firebase is event-driven and asynchronous, so you can't just return it as if this was synchronous code. You need to use a either a callback, a promise, or event handler.
getCars = (userId, callback) ->
id = parseInt(userId)
userRef = new Firebase("https://demo-firebase.firebaseIO.com/users/#{id}/")
userRef.on 'value', (snapshot) ->
if snapshot.val() == null
callback ["toyota"]
else
callback snapshot.val().cars # returns an array of cars
userInfo =
id: user.id
email: user.email
getCars user.id, (cars) ->
userInfo.cars = cars
#Don't user userInfo until here as it's not ready/populated yet!
(Note the node convention is callback(errorOrNull, value), but I'm omitting error handling here for simplicity)
Also note that almost everyone new to async javascript makes this mistake, but it's not a simple syntax gotcha, it's a fundamental thing you at some point (maybe today) you will have the aha/lightbulb moment. The thing to do is step through this in the chrome debugger and note the order each line of code executes in relationship to time. The line with the if statement executes LATER IN TIME after getCars has already returned. And note if you step through it it will skip right over the body of the 'value' event handler because that line just DEFINES the event handler, but it doesn't actually EXECUTE it until the data arrives, so if you want to debug in that, you need to set a breakpoint on the first line of that function (where the if statement is).
There are 3 common paradigms available for this: event binding, promises, and callbacks. All will work. It would be a good exercise for you to code this same functionality with each paradigm and understand that they all basically give you a way to wait for some data to arrive and then run some code in response to the data arriving.