How to await user input with javascripts async/await syntax? - javascript

edit: ATTENTION: following the recommendation by user Barmar in their comment below i will vote to close this more or less opinion/best practise oriented question. I did not delete it but voted to close for there have been answers and productive comments with upvotes already. I reposted this question on https://codereview.stackexchange.com/questions/250351/how-to-await-user-input-with-javascripts-async-await-syntax-in-a-more-functional
I love async/await and Promises since I got my hands on them. And I might be overdoing it, but it feels like there should be a good and readable way to utilize async/await to get a little closer to a flow like, functionalISH programming style.
I would love to not have to only use async/await to wait for web resources to come back but also to wait for user input when I await it.
So far i have some code working similar to this shortened demo where I wrap a one time only EventListener into a Promise:
//// MAIN ///////
(async function(){
//...do some async await stuff here... fetching stuff from a server
// let services = await fetch(...) for example
let services = [{url:"x",label:"1"},{url:"y",label:"2"},{url:"z",label:"3"}]
let service_chosen = await showServicesToUserAndAwaitInput(services);
console.log("service_chosen:",service_chosen);
// ... go on....
})()
//// END MAIN /////
async function showServicesToUserAndAwaitInput(services){
if (services.length < 1){return null}
let choice = null;
let serviceList = document.querySelector("#serviceList");
// show list element
serviceList.classList.remove("hidden")
// create some elements for the user to interact with
for (let service of services){
let button = document.createElement("BUTTON");
button.innerHTML = service.label;
button.addEventListener("click",function(){
document.dispatchEvent(
new CustomEvent('serviceChosen', { detail:service })
)
});
serviceList.appendChild(button);
}
// returns promise with one time only event listener
return new Promise((resolve,reject)=>{
document.addEventListener("serviceChosen",function(e){
serviceList.classList.add("hidden") // hide again for we are done
resolve(e.detail)
},{ once: true })
})
}
.hidden{
visibility: hidden
}
<div id="serviceList" class="hidden">
</div>
But something about this use of the EventListener bugs me. Also: I use a promise that always resolves, which also seems strange.
On the upside: I get to read the code top to bottom and can follow the user flow within the MAIN without having to chase down events, callbacks and so on.
Yet, it feels like I am reinventing something somebody else might have already normed. So:
Is there a better way to achieve this? Are there best practices to work with user interactions or other DOM events in a async and/or thenable way?

So here is an example of your code without async/await and custom events. Progress is done simply by calling appropriate functions. User input is handled by the click event, which listenes for user interaction and triggers appropriate functions.
//// MAIN ///////
let services = [{url:"x",label:"1"},{url:"y",label:"2"},{url:"z",label:"3"}]
showServicesToUserAndAwaitInput(services);
//// END OF MAIN ////
function showServicesToUserAndAwaitInput(services){
if (services.length < 1){return null}
let choice = null;
let serviceList = document.querySelector("#serviceList");
// show list element
serviceList.classList.remove("hidden")
// create some elements for the user to interact with
for (let service of services){
let button = document.createElement("BUTTON");
button.innerHTML = service.label;
button.addEventListener("click",function(){
chooseService(service);
});
serviceList.appendChild(button);
}
function chooseService(service) {
let service_chosen = service;
console.log("service_chosen:",service_chosen);
hideServices();
}
function hideServices() {
serviceList.classList.add("hidden") // hide again for we are done
}
}
.hidden{
visibility: hidden
}
<div id="serviceList" class="hidden">
</div>

Related

Baffled by loops - any help welcome

I’m struggling with making Javascript repeat this code block. It's for a code-operated Phidget switch and works just the once to turn an electronic relay on and off (with a timer for duration) ("Phidget22" is the Node package specific to the device).
I can't find a working method to make the process repeat itself.
Below is the working code, with explanatory notes for what the stages do.
var phidget22 = require('phidget22');
function runExample() {
//Create your Phidget channels
var digitalOutput0 = new phidget22.DigitalOutput();
//Set addressing parameters to specify which channel to open (if any)
digitalOutput0.setHubPort(2);
digitalOutput0.setDeviceSerialNumber(606877);
//Assign any event handlers you need before calling open so that no events are missed.
//Open your Phidgets and wait for attachment
digitalOutput0.open(5000).then(function() {
//Do stuff with your Phidgets here or in your event handlers.
digitalOutput0.setDutyCycle(1);
setTimeout(function () {
//Close your Phidgets once the program is done.
digitalOutput0.close();
process.exit(0);
}, 3000);
});
}
Not sure if I get the idea, but perhaps setInterval would work for you.

Trouble getting DOM update in Vue.js while waiting for promise to complete

I'm not super versed in JS promises though I generally know enough to be dangerous. I'm working on Vue Method that handles searching a large data object present in the this.data() - Normally when I make asynchronous requests via axios this same formatting works fine but in this case I have to manually create a promise to get the desired behavior. Here is a sample of the code:
async searchPresets() {
if (this.presetSearchLoading) {
return
}
this.presetSearchLoading = true; // shows spinner
this.presetSearchResults = []; // removes old results
this.selectedPresetImports = []; // removes old user sections from results
// Need the DOM to update here while waiting for promise to complete
// otherwise there is no "loading spinner" feedback to the user.
const results = await new Promise(resolve => {
let resultSet = [];
for (var x = 0; x < 10000; x++) {
console.log(x);
}
let searchResults = [];
// do search stuff
resolve(searchResults);
});
// stuff after promise
}
The thing is, the stuff after promise works correctly. It awaits the resolution before executing and receives the proper search result data as it should.
The problem is that the DOM does not update upon dispatching the promise so the UI just sits there.
Does anyone know what I'm doing wrong?
Try $nextTick():
Vue 2.1.0+:
const results = await this.$nextTick().then(() => {
let resultSet = []
for (var x = 0; x < 10000; x++) {
console.log(x)
}
let searchResults = []
// do search stuff
return searchResults
});
Any Vue:
const results = await new Promise(resolve => {
this.$nextTick(() => {
let resultSet = []
for (var x = 0; x < 10000; x++) {
console.log(x)
}
let searchResults = []
// do search stuff
resolve(searchResults)
})
})
So it turns out I kind of said it all when I said, "I'm not super versed in JS promises though I generally know enough to be dangerous.".
I appreciate the attempts to help me through this but it turns out that making something a promise does not inherently make it asyncronous. This was my mistake. The problem wasn't that Vue was not updating the DOM, the problem was that the promise code was executing synchronously and blocking - thus because execution never actually stopped to await, Vue had no perogative to update the DOM.
Once I wrapped my promise code in a setTimout(() => { /* all code here then: */ resolve(searchResults); }, 200); Everything started working. I guess the set time out allows the execution to stop long enough for vue to change the dom based on my previous data changes. The script still technically blocks the UI while it runs but at least my loader is spinning during this process which is good enough for what I'm doing here.
See: Are JavaScript Promise asynchronous?
Vue will look for data changes and collect them into an array to tell if the DOM needs to be rerendered afterward. This means that everything in Vue is event(data)-driven. Your function only defines behavior that has no data binding to the V-DOM. So the Vue engine will do nothing since nothing in their dependant data set has changed.
I see your Promise function is going to resolve the response to a variable "searchResults". If your DOM uses that variable, the Vue engine will collect the change after the Promise's done. You may put a property in "data()" and bind it to DOM.
For example:
<span v-for="(res, key) in searchResults" :key="key">
{{ res.name }}
</span>
...
<script>
export default {
...
data () {
return { searchResults: [] }
},
...
}
</script>

Wait for Async result before allowing user to go forward in UI Wizard

I am using a Wizard-UI component that has a handleChangingEvent() function that is called when a user hits the forward/back button. This function returns a boolean for whether the transition should occur. During this method call I need to make an async call to determine whether there are errors on the current Wizard step. The results determine whether i should return true/false. How can I have the handleChangingEvent() function wait until the results have arrived?
This is a tricky computer science problem that has many elaborate solutions but no simple ones in javascript. You have 2 options.
Make the call to the server synchronous, hence blocking the thread until you receive a response.
Override the click handlers on the Prev/Next buttons.
Use something like this solution to store all the current click handlers, then assign your own click handlers that do something like
var can_i_continue = false;
$('#next_btn').on('click', function () {
$.ajax('/can/i/continue', { params : 'foo' })
.then(function (event) {
can_i_continue = true;
runSavedClickHandlers(event);
});
})
wizard.handleChangingEvent = function () {
return can_i_continue;
};

Javascript callbacks gets processed faster than others

I'm using the javascript sdk plugin for facebook to create a feed on my webpage.
The problem is that sometimes during load the feed gets unordered, even if i have setup a callback chain.
I think it gets unordered because sometimes the "second" async call gets processed faster than the "first" async call.
This is the first time i've been using callbacks, am i doing it right?
How can i solve the feed gets unordered if some calls finish faster than others?
The code below is only the relevant code and is under working status.
function initFeed(){
FB.api('/{id}/feed', function(response){
var feedArray = response.data;
$.each(feedArray, function(){
var $this = $(this)[0]; //Status Object for single Status in Feed
setStatus($this, processStatus); //processStatus is function defined below
});
});
}
function setStatus(statusObject, callbackProcessStatus){
FB.api("/{personId}?fields=id,link,name,picture",
function (response) {
var html = /* Generates html based from statusObject and response */
callbackProcessStatus(html);
});
}
function processStatus(html){
$('#fb-status-wrapper').append(html);
}
(was uncertain on the title of this post, please edit if you think it is not descriptive enough)
Best regards
This is a somewhat common problem with parallel async calls. The simplest solution requires promises. I recommend the Bluebird promise library, but most will do fine.
var fbApi = function(url){
return new Promise(function(resolve, reject){
FB.api(url, function(resp){ resolve(resp); });
});
}
function setStatus(statusObject){
return fbApi("/{personId}?fields=id,link,name,picture")
.then(function(response){
var html = ...;
return html;
});
}
function getFeedItemPromises(){
return fbApi("/{id}/feed").then(function(response){
return response.data.map(function(item){
});
});
}
Depending on your needs, initFeed could be one of these. The first renders the feed when all items are available, and the second renders it when each item is available, but enforces the order.
function initFeed(){
return Promise.all(getFeedItemPromises())
.then(function(itemsHtml){
// append all of the items at once
$('#fb-status-wrapper').append(itemsHtml.join("\n"));
});
}
Or this which ensures the order, but eagerly appends items to the feed, after all previous items have been added.
function initFeed(){
function renderItem(html){
$('#fb-status-wrapper').append(html);
}
// reduce can be used to chain promises in sequence
return getFeedItemPromises().reduce(function(p, nextPromise){
return p.then(function(){ return nextPromise })
.then(renderItem);
}, Promise.resolve())
}
An alternative would be to create a div for each item which acts as a placeholder, keep those in an array, and fill them in when each resolves. This works especially well if you know the height of the items beforehand, and fade them in when they load. From a UX perspective, this is the best in my opinion.
I would not recommend the above if you don't know the heights of items, as it'll cause headache inducing shifting of items as new ones are inserted.
Indeed you cannot rely on the order in which the requests will finish. The only way to be sure, is to only call the second one if the first one is done. But that will slow down the loading quite a lot.
Another possibility is to remember for each request which one it is, and insert the items in the right order (insert before a 'later' one, even if that one was received earlier).
I think the easiest way to do that, is to make placeholders for the items inside the each loop, so the placeholders are inserted in the right order. When the requests return, you just place the responses in the right placeholder.
It could look somewhat like this. 2 extra lines and a couple of tiny changes. I couldn't test this without the API, but I hope you get the idea.
function initFeed(){
FB.api('/{id}/feed', function(response){
var feedArray = response.data;
$.each(feedArray, function(index){
var $this = $(this)[0]; //Status Object for single Status in Feed
// Make a container per item inside the wrapper.
var $itemContainer = $('<div></div>');
$('#fb-status-wrapper').append($itemContainer);
// Pass the container to the api function.
setStatus($this, processStatus, $itemContainer); //processStatus is function defined below
});
});
}
function setStatus(statusObject, callbackProcessStatus, $container){
FB.api("/{personId}?fields=id,link,name,picture",
function (response) {
var html = /* Generates html based from statusObject and response */
// Pass the item place holder/container to the processing procedure.
callbackProcessStatus(html, $container);
});
}
function processStatus(html, $container){
$container.append(html);
}

multithreading And Subscribe/Publish approach in javascript

I understand that there is no multithreading support in javascript. And i wanted some expert advice on the below scenario..
My requirement is to perform a AJAX call and upon successful completetion, i want to trigger set of events (to update the different parts of UI parallely)
I am planned to use Subscribe/Publish pattern, is it possible to subscribe multiple listners to the AJAX completion event.
If possible, i wanted to know how these listners notified on publish.. (parallely in muthithreaded way or one by one).
And suggest me the best way to achive this one.. I really appreciate your thoughts.
EDIT::
I know there are popular frameworks like JQuery supports this pattern. But am in a situation to develop this functionality from a scratch (we have our own framework).
I've a Request Pooler that might give you a good head-start here. [Since this answer was accepted I've retired the pooler in favor of a more complete "AJAX Helper" - the link has been updated.]
I'm not sure that'll do everything you want (although it sounds like it may be close). It's old, but it works:
Depressed Press DP_AJAX
It supports multiple simultaneous requests with timeout/retry, per-request handlers, has a very small footprint and can be combined with other code easily.
You create a pool (telling it how many simultaneous requests are allowed) and then toss requests at it. When they're done they call whatever handler you specified.
A small, complete example of it's use:
// The handler function
function AddUp(Num1, Num2, Num3) {
alert(Num1 + Num2 + Num3);
};
// Instantiate the Pool
myRequestPool = new DP_RequestPool(4);
// Start the Interval
myRequestPool.startInterval(100);
// Create the Request
myRequest = new DP_Request(
"GET",
"http://www.mysite.com/Add.htm",
{"FirstNum" : 5, "SecondNum" : 10},
AddUp,
[7,13]);
// Add the request to the queue
myRequestPool.addRequest(myRequest);
It's open source - feel free to chop/fold/spindle or mutilate it to your hearts content.
Jim Davis
This article describes what you're trying to accomplish pretty closely. Essentially you just have a JavaScript file that holds an array of handlers/subscribers. Each subscriber registers itself with the publisher (i.e. gets added to the handlers array). Then in the onClose handler of your Ajax call, you'd call a function that iterates over the subscribers and notifies them each by name:
this.handlers = [];
...
for(var i = 0; i < this.handlers.length; i++)
{
this.handlers[i].eventHandler.call(this, eventArgs);
}
...
Could this serve as a ligthweight message passing framework?
function MyConstructor() {
this.MessageQueues = {};
this.PostMessage = function (Subject) {
var Queue = this.MessageQueues[Subject];
if (Queue) return function() {
var i = Queue.length - 1;
do Queue[i]();
while (i--);
}
}
this.Listen = function (Subject, Listener) {
var Queue = this.MessageQueues[Subject] || [];
(this.MessageQueues[Subject] = Queue).push(Listener);
}
}
then you could do:
var myInstance = new MyConstructor();
myInstance.Listen("some message", callback());
myInstance.Listen("some other message", anotherCallback());
myInstance.Listen("some message", yesAnotherCallback());
and later:
myInstance.PostMessage("some message");
would dispatch the queues
This is possible and you should really use a library for that to avoid all the browser incompatibilities. For example jQuery provides Ajax functionality that lets you execute code on completion of the Ajax call. See here for documentation.
if you want to fire functions in parralell use following:
this.handlers = [];
...
for(var i = 0; i < this.handlers.length; i++)
{
setTimeout(function(){this.handlers[i].eventHandler.call(this, eventArgs);},0);
}
...

Categories

Resources