This question already has answers here:
Why is my variable unaltered after I modify it inside of a function? - Asynchronous code reference
(7 answers)
Closed 1 year ago.
I am trying to figure out the best way to wait on the results of a subscription where I am passing in a callback. For example let's say I have the following:
function someMethod() {
let someResult = null;
someSubPub.subscribe(someTopic, someKey, someValue, (someResponse) => {
someResult = //do stuff in the callback with someResponse
});
return someResult;
}
I'm not quite sure how await would correctly work in this situation, aka blocking inside a callback.
Async/Await are nice, but sometimes it's simpler to think about the Promise construct that async/await abstracts away.
I suggest:
function someMethod() {
return new Promise((resolve, reject) => {
someSubPub.subscribe(someTopic, someKey, someValue, () => {
const someResult = //do stuff in the callback
resolve(someResult);
});
});
}
If you don't want to work with promises directly, you can wrap someSubPub.subscribe to return a promise
function someSubPubSubscribePromise(topic, key, value) {
return new Promise((resolve, reject) => {
someSubPub.subscribe(topic, key, value, resolve);
});
}
async function someMethod() {
await someSubPubSubscribePromise(someTopic, someKey, someValue);
const someResult = //do stuff in the callback
return someResult;
}
In either of those examples, you can do const result = await someMethod() (you can await both async methods and regular methods that return a promise)
One thing to consider: usually a pub/sub interface can call the callback multiple times. An async method / a method returning a promise can only resolve or reject exactly once. Probably you should be unsubscribing in the callback after you've responded to the event once?
Related
I'm trying to write a "mixing" for JavaScript classes (controllers, in my app) to automatically "await" for a given function to be resolved, before actually invoke the real methods. Real class methods should receive the resolved value as last argument.
Here is the code of useAwait, where i'm looking for the static class property awaits and wrapping the originalFunc into a new async one. I'm calling the new function passing the original arguments plus the asyncFn call result:
const useAwait = (controller, asyncFn) => {
controller.constructor.awaits.forEach(func => {
const originalFunc = controller[func];
controller[func] = async (...args) => {
return originalFunc.apply(
controller,
[...args, await asyncFn.call(controller)]
);
};
});
}
So when useAwait(ctrl, this.load) is invoked on an instance this class:
class Controller {
static awaits = ['foo', 'bar'];
promise;
constructor() {
useAwait(this, this.load);
}
async foo(e, resolved) {
return resolved;
}
bar(resolved) {
return resolved;
}
async load() {
if (!this.promise) {
this.promise = new Promise(resolve => setTimeout(() => {
resolve('Hello World!');
}, 3000));
}
return this.promise;
}
}
The problem: all seems fine for foo (already async), but it's not for bar: the result is a Promise because now the bar is wrapped in async (wan't before). I know that an async function result is wrapped into a Promise. Codepen example where bar call outputs "[object Promise]".
So the question is: in theory, I should check if the original function is async and if it was not, await for it's return value?
...in theory, I should check if the original function is async and if it was not, await for it's return value?"
It wouldn't matter, your wrapper is async; an async function always returns a promise, whether you use await or not. Moreover, your wrapper can't be synchronous, because it needs to call awaitFn (load, in the example) and wait for its result.
If you're going to wrap originalFunction (bar) such that it waits for awaitFn (load) to complete, the wrapped version of it needs to be asynchronous (either async, or return a promise explicitly [or accept a callback, but better IMHO to use promises]). It cannot be synchronous, because awaitFn (load) isn't synchronous.
If the class instance isn't ready for use when you construct it, you might consider using a static method to get an instance instead; the static instance would return a promise that it fulfills with the instance once load is complete. Rough sketch:
class Controller {
dataFromLoadingProcess;
constructor(dataFromLoadingProcess) {
this.dataFromLoadingProcess = dataFromLoadingProcess;
}
async foo(e, resolved) {
// ...optionally use `this.dataFromLoadingProcess`...
return resolved;
}
bar(resolved) {
// ...optionally use `this.dataFromLoadingProcess`...
return resolved;
}
static async createInstance() {
await /*...the loading process...*/;
return new Controller(/*...data from loading process here, perhaps...*/)
}
}
This question already has answers here:
Why is my variable unaltered after I modify it inside of a function? - Asynchronous code reference
(7 answers)
Closed 1 year ago.
I want to assign the value inside a callback function to a variable outside of it. Here is an example with child_process.
callback.js
let variable;
require('child_process').spawn('python', ['./hello.py']).stdout.on('data', data => {
variable = data.toString();
console.log(variable);
});
console.log(variable);
hello.py
print('Hello World')
output:
undefined
Hello World
I know that the console.log at the bottom is being executed before the callback function and therefore the variable is still undefined. But how could I wait for the callback function to finish first?
Solution:
Promises seem to be the optimal answer and the code needs to be asynchronous.
async function test() {
const promise = new Promise((resolve, reject) => {
require('child_process').spawn('python', ['./test.py']).stdout.on('data', data => {
resolve(data.toString());
});
});
return promise;
}
(async function () {
let variable;
await test().then(res => {
variable = res;
});
console.log(variable);
}())
You can use a promise to wait for the callback like in this example:
let data;
const myPromise = new Promise((resolve, reject) => {
setTimeout(() => {
data="abc";
resolve('foo');
}, 300);
});
myPromise.then((res)=>{console.log(data+res)})
this will print out "abcfoo"
Often the best way to wait for something asynchronous is to have asynchronous code yourself.
I would like to implement chained method calls like
observable
.pipe(
filter('foo'),
add(3)
)
.subscribe(subscriber);
In order for this to work, the result of .pipe(...) must provide the method subscribe.
I would like to allow some of the chained method calls (e.g. pipe) to by async. However, this would breaks my chain because the promise returned by pipe does not have a subscribe method:
await observable
.pipe(
filter('foo'),
add(3)
)
.subscribe(subscriber);
async pipe(...operators){
...
}
=> Uncaught (in promise) TypeError: observable.pipe(...).subscribe is not a function
I could rewrite my main code to
observable
.pipe(
filter('foo'),
add(3)
).then(pipeResult=>
pipeResult.subscribe(subscriber);
);
However, I find that very ugly to read.
=> Is there a way to apply await for each call in the chain of method calls and not only for the last one?
I would expect something like
awaitEach observable
.pipe(
filter('foo'),
add(3)
)
.subscribe(subscriber);
Edit
Related question:
chaining async method calls - javascript
With the help of Promises I could transform from synchronous to asynchronous calls:
foo(){
return new Promise(resolve=>{
baa().then(arg=>resolve(arg))
})
}
However, I need the other direction, something like:
pipe() {
var result = undefined;
await asyncCall(()=>{ //await is not allowed here; forces pipe to by async
result = 5;
});
return result;
}
As a work around, I extended the resulting promise with a subscribe proxy, calling my actual subscribe method:
pipe(...operators){
let observable = this;
let promise = new Promise(async (resolve) =>{
for (let operator of operators){
observable = await this._applyOperator(operator, observable);
}
resolve(observable);
});
promise.subscribe = (subscriber)=>{
promise.then(resultingObservable =>{
resultingObservable.subscribe(subscriber);
})
};
return promise;
}
If you know a better solution, please let me know.
I'd like to accomplish the following using promises: only execute further once the state of something is ready. I.e. like polling for an external state-change.
I've tried using promises and async-await but am not getting the desired outcome. What am I doing wrong here, and how do I fix it?
The MDN docs have something similar but their settimeout is called within the promise--that's not exactly what I'm looking for though.
I expect the console.log to show "This function is now good to go!" after 5 seconds, but instead execution seems to stop after calling await promiseForState();
var state = false;
function stateReady (){
state = true;
}
function promiseForState(){
var msg = "good to go!";
var promise = new Promise(function (resolve,reject){
if (state){
resolve(msg);
}
});
return promise;
}
async function waiting (intro){
var result = await promiseForState();
console.log(intro + result)
}
setTimeout(stateReady,5000);
waiting("This function is now ");
What you're doing wrong is the promise constructor executor function executes immediately when the promise is created, and then never again. At that point, state is false, so nothing happens.
Promises (and async/await) are not a replacement for polling. You still need to poll somewhere.
The good news: async functions make it easy to do conditional code with loops and promises.
But don't put code inside promise constructor executor functions, because of their poor error handling characteristics. They are meant to wrap legacy code.
Instead, try this:
var state = false;
function stateReady() {
state = true;
}
const wait = ms => new Promise(resolve => setTimeout(resolve, ms));
async function promiseForState() {
while (!state) {
await wait(1000);
}
return "good to go!";
}
async function waiting(intro) {
var result = await promiseForState();
console.log(intro + result)
}
setTimeout(stateReady,5000);
waiting("This function is now ");
Based on your comments that you are waiting for messages from a server it appears you are trying to solve an X/Y problem. I am therefore going to answer the question of "how do I wait for server messages" instead of waiting for global variable to change.
If your network API accepts a callback
Plenty of networking API such as XMLHttpRequest and node's Http.request() are callback based. If the API you are using is callback or event based then you can do something like this:
function myFunctionToFetchFromServer () {
// example is jQuery's ajax but it can easily be replaced with other API
return new Promise(function (resolve, reject) {
$.ajax('http://some.server/somewhere', {
success: resolve,
error: reject
});
});
}
async function waiting (intro){
var result = await myFunctionToFetchFromServer();
console.log(intro + result);
}
If your network API is promise based
If on the other hand you are using a more modern promise based networking API such as fetch() you can simply await the promise:
function myFunctionToFetchFromServer () {
return fetch('http://some.server/somewhere');
}
async function waiting (intro){
var result = await myFunctionToFetchFromServer();
console.log(intro + result);
}
Decoupling network access from your event handler
Note that the following are only my opinion but it is also the normal standard practice in the javascript community:
In either case above, once you have a promise it is possible to decouple your network API form your waiting() event handler. You just need to save the promise somewhere else. Evert's answer shows one way you can do this.
However, in my not-so-humble opinion, you should not do this. In projects of significant size this leads to difficulty in tracing the source of where the state change comes form. This is what we did in the 90s and early 2000s with javascript. We had a lot of events in our code like onChange and onReady or onData instead of callbacks passed as function parameters. The result was that sometimes it takes you a long time to figure out what code is triggering what event.
Callback parameters and promises forces the event generator to be in the same place in the code as the event consumer:
let this_variable_consumes_result_of_a_promise = await generate_a_promise();
this_function_generate_async_event((consume_async_result) => { /* ... */ });
From the wording of your question you seem to be wanting to do this instead;
..somewhere in your code:
this_function_generate_async_event(() => { set_global_state() });
..somewhere else in your code:
let this_variable_consumes_result_of_a_promise = await global_state();
I would consider this an anti-pattern.
Calling asynchronous functions in class constructors
This is not only an anti-pattern but an impossibility (as you've no doubt discovered when you find that you cannot return the asynchronous result).
There are however design patterns that can work around this. The following is an example of exposing a database connection that is created asynchronously:
class MyClass {
constructor () {
// constructor logic
}
db () {
if (this.connection) {
return Promise.resolve(this.connection);
}
else {
return new Promise (function (resolve, reject) {
createDbConnection(function (error, conn) {
if (error) {
reject(error);
}
else {
this.connection = conn; // cache the connection
resolve(this.connection);
}
});
});
}
}
}
Usage:
const myObj = new MyClass();
async function waiting (intro){
const db = await myObj.db();
db.doSomething(); // you can now use the database connection.
}
You can read more about asynchronous constructors from my answer to this other question: Async/Await Class Constructor
The way I would solve this, is as follows. I am not 100% certain this solves your problem, but the assumption here is that you have control over stateReady().
let state = false;
let stateResolver;
const statePromise = new Promise( (res, rej) => {
stateResolver = res;
});
function stateReady(){
state = true;
stateResolver();
}
async function promiseForState(){
await stateResolver();
const msg = "good to go!";
return msg;
}
async function waiting (intro){
const result = await promiseForState();
console.log(intro + result)
}
setTimeout(stateReady,5000);
waiting("This function is now ");
Some key points:
The way this is written currently is that the 'state' can only transition to true once. If you want to allow this to be fired many times, some of those const will need to be let and the promise needs to be re-created.
I created the promise once, globally and always return the same one because it's really just one event that every caller subscribes to.
I needed a stateResolver variable to lift the res argument out of the promise constructor into the global scope.
Here is an alternative using .requestAnimationFrame().
It provides a clean interface that is simple to understand.
var serverStuffComplete = false
// mock the server delay of 5 seconds
setTimeout(()=>serverStuffComplete = true, 5000);
// continue until serverStuffComplete is true
function waitForServer(now) {
if (serverStuffComplete) {
doSomethingElse();
} else {
// place this request on the next tick
requestAnimationFrame(waitForServer);
}
}
console.log("Waiting for server...");
// starts the process off
requestAnimationFrame(waitForServer);
//resolve the promise or whatever
function doSomethingElse() {
console.log('Done baby!');
}
Asynchronous calls are an inherent part of javascript, and using callbacks is often an elegant tool to handle these calls.
However, I am not quite sure how is the branching of code following an asynchronous operation decided. For example, what would happen with the following code?
function f(callback) {
value = some_async_call();
if (value) {
callback(value);
}
return(value);
}
What would happen here? From my short JS experience, return would send back an undefined value. But suppose that value returns true from the asynchronous call, would the callback be called with the right value or with an undefined value?
In other words, is there a rule regarding which operations are executed immediately after the async call, and which are deferred until the value is returned?
What have I tried before asking
SFTW for branching asynchronous calls in javascript, but found nothing canonical or decisive.
update: added a practical difference between the 3 different approaches at the bottom
let's assume some_async_call(); is defined as an async function: async function some_async_call() { ... }
what this function returns is a Promise, which means that value is now a promise: value.then( function(result) { } )
when i translate this into code:
async function some_async_call() {
if (theMoonIsInTheRightPosition)
return Fetch('/api/data/') // this returns a promise as well.
return false;
}
i can now do 2 things:
function parentFunction(callback) {
var promise = some_async_call();
promise.then( callback );
return ...; // you can't "return" the value of an async call synchronously, since it is a promise.
}
or:
async function asyncParentFunction( callback ) {
var value = await some_async_call();
if (value)
callback( value );
return value;
}
however, this transforms the parent-function into an async function as well, which means the immediate return value of that function... is a promise as well.
Long story short:
You either use callbacks to flow through your asynchronous functions, or promises, or async/await
callbacks
function doStuff(callback) {
// do asynchronous stuff
var result = 100;
callback(result); // once you're done with stuff
}
doStuff( function(data) { console.log('Im done!', data); } );
promises
function doStuff() {
return new Promise(function(resolve, reject) {
// do asynchronous stuff
var result = 100;
resolve(result);
});
}
doStuff.then(function(data) { console.log('Im done!', data); });
async/await
function doStuff() {
return new Promise(function(resolve, reject) {
// do asynchronous stuff
var result = 100;
resolve(result);
});
}
(async function() { // async/await only works in async functions.
var data = await doStuff();
console.log('Im done!', data);
})();
as you can see: promises and async/await use the same mechanism and are really worth reading into.
a practical example of the difference between the three:
callbacks
function fetchUserWithPosts(userId, callback) {
fetchUser(userId, function(user) {
fetchPostsByUserId(userId, function(posts) {
callback({
user: user,
posts: posts
});
});
});
}
promises
function fetchUserWithPosts(userId) {
return Promise.all([
fetchUser(userId),
fetchPostsByUserId(userId)
]).then(function(result) {
return {
user: result[0],
posts: result[1]
};
});
}
async/await
async function fetchUserWithPosts(userId) {
return {
user: await fetchUser(userId),
posts: await fetchPostsByUserId(userId);
};
}
You are correct, undefined will be returned. You execute your logic after asynchronous operation you should pass your callback into that function. You are trying to use code in synchronous manner and async/await comes to the rescue. Assuming that some_async_call returns Promise you can write:
async function f(callback) {
var value = await some_async_call();
if (value) {
callback(value);
}
return value;
}
In this case your function will return Promise of value and also will be asynchronous. Read more here.
And even more you don't need to pass a callback. You can await your function in client code and execute callback from there.
Regarding branching, the only way you can branch without using async/await is to make some_async_call accept a callback that accepts the value as parameter:
function f(callback) {
some_async_call(function(value) {
if (value) {
callback(value);
}
});
}
And to reiterate once again there is no way to return a value from async method except Promise. Or Observable in RxJS.