Related
I want to call a function from a method. I don't even know how to ask correctly. test.onload() is called immediately, not after 3 seconds. See code for example, please.
export default class {
constructor() {
// some code
setTimeout(() => {
this.onload;
}, 3000);
}
onload = (fn) => {
console.log('loaded event');
fn();
};
}
const test = new TEST();
test.onload(function () {
console.log('from instance');
});
You are calling the function directly. Somehow you think that will be called when the setTimeout runs which is not the case.
If you want the function "from instance" to be called you need to rethink how you are registering it. You are going to have to store the function somehow and let the timer pick it up and execute it.
Setting it with the constructor
class TEST {
constructor(callback) {
this.callback = callback;
setTimeout(() => this.onload(), 3000);
}
onload = () => {
console.log('loaded event');
if(this.callback) this.callback();
};
}
const test = new TEST(function () {
console.log('from instance');
});
Setting it with a method
class TEST {
constructor() {
setTimeout(() => this.onload(), 3000);
}
onload = () => {
console.log('loaded event');
if(this.callback) this.callback();
};
registerCallback(callback) {
this.callback = callback;
}
}
const test = new TEST();
test.registerCallback(function () {
console.log('from instance');
});
I have been exercising callbacks and I got this exercise that I have been struggling with the syntax. Here is my code:
function wash(callback) {
setTimeout(function() {
console.log('wash');
callback();
}, 3000);
}
function dry(callback) {
setTimeout(function() {
console.log('dry');
callback();
}, 2000);
}
function fold(callback) {
setTimeout(function() {
console.log('fold');
callback();
}, 1000);
}
doLaundry([wash, dry, fold]);
Invoking the doLaundry([wash,dry,fold]) is supposed to print out the following:
wash dry fold Done!
by calling this function (needs work)
function doLaundry() {
// Call functions
}
I tried this and it was close but the syntax is wrong or uneeded:
function doLaundry(actions, callback) {
actions.forEach((item, index) => {
wash(item, (error) => dry(item, (error) => fold(item, (error) => {})));
});
}
I'm really confused how to implement the doLaundry() with callback after checking out other simple tutorials. Any suggestions how to approach this?
Since the functions don't need to be asynchronous, I think you're making this a lot more complicated than it needs to be. Just iterate through the array of actions (functions) and call them. Then log Done at the end.
function wash() {
console.log('wash');
}
function dry() {
console.log('dry');
}
function fold() {
console.log('fold');
}
const doLaundry = (fns) => {
fns.forEach(fn => {
fn();
});
console.log('Done!');
}
doLaundry([wash, dry, fold]);
Or, if you must stick with the asynchronous functions, you can build up the callback chain starting from the last one in the chain and going backwards with reduceRight:
function wash(callback) {
setTimeout(function() {
console.log('wash');
callback();
}, 3000);
}
function dry(callback) {
setTimeout(function() {
console.log('dry');
callback();
}, 2000);
}
function fold(callback) {
setTimeout(function() {
console.log('fold');
callback();
}, 1000);
}
const doLaundry = (fns) => {
const firstCallback = () => console.log('done');
const firstFn = fns.reduceRight(
(nextCallback, fn) => () => fn(nextCallback),
firstCallback
);
firstFn();
}
doLaundry([wash, dry, fold]);
Or, for an easier to understand approach, promisify each callback and await them:
function wash(callback) {
setTimeout(function() {
console.log('wash');
callback();
}, 3000);
}
function dry(callback) {
setTimeout(function() {
console.log('dry');
callback();
}, 2000);
}
function fold(callback) {
setTimeout(function() {
console.log('fold');
callback();
}, 1000);
}
const toProm = fn => new Promise(resolve => fn(resolve));
const doLaundry = async (fns) => {
for (const fn of fns) {
await toProm(fn);
}
console.log('done');
}
doLaundry([wash, dry, fold]);
Try this simple code:
only one thing that you have to remember is, in the callback function if we are passed arguments, you have to write something in front of the callback like this
()=>
function wash(dry) {
console.log("wash");
setTimeout(dry, 3000);
}
function dry(fold) {
console.log("dry");
setTimeout(fold, 2000);
}
function fold(done) {
console.log("fold");
console.log("done");
}
setTimeout(() => wash(() => dry(() => fold("done"))), 4000);
function wash(callback){
setTimeout(()=>{
console.log('wash');
callback()
},3000)
}
function dry(callback){
setTimeout(()=>{
console.log('dry');
callback()
},2000)
}
function fold(callback){
setTimeout(()=>{
console.log('fold');
callback()
},1000)
}
function doLaundry(callback){
callback()
}
doLaundry(()=>{
wash(()=>{
dry(()=>{
fold(()=>{
console.log('done')
})
})
})
});
This one should do the job.
function doLaundry(actions) {
const [_wash, _dry, _fold] = actions
_wash(() => {
_dry(() => {
_fold(() => {
console.log('Done')
})
})
})
}
First line is destructuring the array, so it's elements are easier to access:
const [_wash, _dry, _fold] = actions
Function doLaundry takes array of callback functions as argument, which means when calling those functions inside of body of doLaundry, only names which it takes from arguments should be provided together with arguments that those callback functions are taking.
Since all of them take callbacks as arguments, arguments must be another function. First callback function's (in this case _wash) argument is another callback (_dry), therefore it takes another function as argument. If _wash's argument wasn't another function that takes callback as argument, it would be done like this:
_wash(_dry)
Last callback (_fold) also takes a callback as an argument which is in this case an anonymous function.
I have found that a lot of my API calls functions change loading property to true in the beginning and to false after it's finished. The thing is I have a lot of functions and I like to keep my code DRY.
So, I came up with something like this:
async loadingWrap (func, ...args) {
this.loading = true
await func(...args)
this.loading = false
}
and when I call it is like this:
await this.loadingWrap(
this.someAsyncFunction, { param: 'Value' }
)
where what ideally I want would be:
await this.loadingWrap(this.someAsyncFunction({ param: 'Value'}))
so it will look like a normal function to the future reader (me or someone else).
Is that possible? I looked at higher-order functions, but no luck so far.
You can almost get what you want. What you need to do is to not pass the arguments and call the function without any arguments. This behaves similarly to native functions that accept callbacks like setTimeout() or addEventListener():
async loadingWrap (func) {
this.loading = true
await func()
this.loading = false
}
Then call it similar to how you'd call functions like setTimeout():
await this.loadingWrap(() => this.someAsyncFunction({ param: 'Value'}))
The trick is to wrap your function in an anonymous function that accepts no arguments - just like other functions like it in the js world.
Here's a full working demo with console.log replacing the loading variable:
async function loadingWrap (func) {
console.log('loading');
await func()
console.log('done loading');
}
function timer (x) {
return new Promise((ok,fail) => setTimeout(ok,x));
}
async function test () {
console.log('calling async function');
await loadingWrap(() => timer(2000));
console.log('finished calling async function');
}
test();
From what you want:
await this.loadingWrap(this.someAsyncFunction({ param: 'Value'}))
This won't work because it will treat the parameter as a nested function.
The order of operations will be:
Call this.someAsyncFunction({ param: 'Value'})
Call this.loadingWrap(x) where x is the return value of step 1
This type of function evaluation is exactly like mathematical functions, where to evaluate f(g(x)) (f of g, given x), you first evaluate g given the value x, and then use the result to evaluate f.
A possible solution...
You might be able to use JavaScript's Proxy object. As the docs say, you can use them on a function by using the apply trap.
You'll write your handler generically to handle any function trying to use a loading flag.
const handler = {
apply: async function(target, thisArg, argumentsList) {
thisArg.loading = true
await target.apply(thisArg, argumentsList)
thisArg.loading = false
}
}
You will then create your someAsyncFunction member function by creating the proxy like this:
YourClass.prototype.someAsyncFunction = new Proxy(someAsyncFunction, handler);
Then you call it like this:
// inside some other async member function...
await this.someAsyncFunction({ param: 'Value'})
Here is a run-able example (there's nothing on the page, just console output):
class MyObj {
constructor() {
this.loading = false
}
async someAsyncFunction(val) {
console.log(`entering someAsyncFunction: loading = ${this.loading}`)
console.log(`calling this.asyncLoad...`)
await this.asyncLoad({
value: val
})
console.log(`exiting someAsyncFunction: loading = ${this.loading}`)
}
}
async function asyncLoad(params) {
return new Promise(resolve => {
console.log(`entering asyncLoad: loading = ${this.loading}, value = ${params.value}`)
setTimeout(() => {
console.log(`exiting asyncLoad: loading = ${this.loading}, value = ${params.value}`)
resolve()
}, 1000)
})
}
const handler = {
apply: async function(target, thisArg, argumentsList) {
console.log('PROXY: setting load to true...')
thisArg.loading = true
console.log('PROXY: calling the proxied function...')
await target.apply(thisArg, argumentsList)
console.log('PROXY: setting load to false...')
thisArg.loading = false
}
}
MyObj.prototype.asyncLoad = new Proxy(asyncLoad, handler);
async function run() {
let myobj = new MyObj()
console.log(`in run, before calling someAsyncFunction, loading = ${myobj.loading}`)
setTimeout(() => {
console.log(`INTERRUPT: checking loading is true (${myobj.loading})`)
}, 500)
await myobj.someAsyncFunction(1)
console.log(`in run, after calling someAsyncFunction, loading = ${myobj.loading}`)
}
run()
Selective Proxy-ing
If the function you're trying to call is generic enough that you only need to perform Proxy actions sometimes, this is entirely do-able. This is also where Proxy becomes really cool, because you can create different proxies to perform different actions while maintaining the same base code.
In the example below, asyncLoad is my generic function, and I can call it providing an instance of ObjWithoutStatus as the function's this context. But I also created two proxies, one to set the loading status, and another to set the loaderIsRunning status. Each of these end up calling the base function, without having to perform the gymnastics of creating wrappers that maintain the correct scope.
class ObjWithoutStatus {
constructor() {}
}
class ObjWithLoading {
constructor() {
this.loading = false
}
}
class ObjWithLoaderIsRunning {
constructor() {
this.loaderIsRunning = false
}
}
async function asyncLoad(params) {
return new Promise(resolve => {
console.log(`entering asyncLoad: loading = ${this.loading}, value = ${params.value}`)
setTimeout(() => {
console.log(`exiting asyncLoad: loading = ${this.loading}, value = ${params.value}`)
resolve()
}, 1000)
})
}
const handler_loading = {
apply: async function(target, thisArg, argumentsList) {
console.log('PROXY_loading: setting load to true...')
thisArg.loading = true
console.log('PROXY_loading: calling the proxied function...')
await target.apply(thisArg, argumentsList)
console.log('PROXY_loading: setting load to false...')
thisArg.loading = false
}
}
const handler_loaderIsRunning = {
apply: async function(target, thisArg, argumentsList) {
console.log('PROXY_loaderIsRunning: setting load to true...')
thisArg.loaderIsRunning = true
console.log('PROXY_loaderIsRunning: calling the proxied function...')
await target.apply(thisArg, argumentsList)
console.log('PROXY_loaderIsRunning: setting load to false...')
thisArg.loaderIsRunning = false
}
}
const asyncLoad_loading = new Proxy(asyncLoad, handler_loading)
const asyncLoad_loaderIsRunning = new Proxy(asyncLoad, handler_loaderIsRunning)
const x = new ObjWithoutStatus()
const y = new ObjWithLoading()
const z = new ObjWithLoaderIsRunning()
async function run() {
console.log(`in run, before calling asyncLoad, x.loading, x.loaderIsRunning = ${x.loading}, ${x.loaderIsRunning}`)
setTimeout(() => console.log(`INTERRUPT_asyncLoad: x.loading, x.loaderIsRunning = ${x.loading}, ${x.loaderIsRunning}`), 500)
await asyncLoad.call(x, {
value: 1
})
console.log(`in run, after calling asyncLoad, x.loading, x.loaderIsRunning = ${x.loading}, ${x.loaderIsRunning}`)
console.log(`in run, before calling asyncLoad_loading, y.loading = ${y.loading}`)
setTimeout(() => console.log(`INTERRUPT_asyncLoad_loading: y.loading = ${y.loading}`), 500)
await asyncLoad_loading.call(y, {
value: 2
})
console.log(`in run, after calling asyncLoad_loading, y.loading = ${y.loading}`)
console.log(`in run, before calling asyncLoad_loaderIsRunning, z.loaderIsRunning = ${z.loaderIsRunning}`)
setTimeout(() => console.log(`INTERRUPT_asyncLoad_loading: z.loaderIsRunning = ${z.loaderIsRunning}`), 500)
await asyncLoad_loaderIsRunning.call(z, {
value: 3
})
console.log(`in run, after calling asyncLoad_loaderIsRunning, z.loaderIsRunning = ${z.loaderIsRunning}`)
}
run()
It is possible, but you need to make sure the this value is correctly set when the actual function is called:
async loadingWrap (func, thisArg, ...args) {
this.loading = true;
// support functions that resolve to something useful. And provide `this`
let result = await func.apply(thisArg, args);
this.loading = false
return result;
}
And in some async method:
let result = await this.loadingWrap(
this.someAsyncFunction, this, { param: 'Value' }
);
console.log(result);
If you don't like the extra parameter, then you must pass a callback function that sets the this binding correctly, and then you might as well settle the arguments at the same time:
async loadingWrap (func) {
this.loading = true;
let result = await func();
this.loading = false
return result;
}
And in some async method, note the callback function:
let result = await this.loadingWrap(
() => this.someAsyncFunction({ param: 'Value' })
);
console.log(result);
You're looking for a higher order function, which is just a function that returns a function. Lodash uses techniques like this for functions like throttle or debounce.
// Wrap your function in another function that sets the loading property.
// We're passing "this" as "that" to preserve it when loadStuff is called.
function loadingWrap(that, functionToWrap) {
return async function() {
that.loading = true;
let returnVal = await functionToWrap.apply(that, arguments);
that.loading = false;
return returnVal;
}
}
// In your API class
public loadStuff1 = loadingWrap(this, (arg1, arg2) => {
// This method must return a promise for the loading wrap to work.
return http.get('someURL', arg1, arg2);
});
// In the class that uses your api
myAPI.loadStuff1('abc', 123);
Consider using wrapper like this:
function bar(fn) {
console.log('before');
fn();
console.log('after');
}
function baz(...params) {
console.log('inside', params);
}
bar(() => baz(1, 2, 3));
class A {
constructor() {
this.loading = false;
}
async loadable(fn) {
this.loading = true;
await fn();
this.loading = false;
}
async load() {
return new Promise(res => setTimeout(res, 2000))
}
async fetch() {
this.loadable(this.load); // or () => this.load(params)
}
}
new A().fetch();
Actual scenario is i want to return request promise response of test2() from test4() function.
because some independent calculation are done in test4() and test2() can not wait for test3() and test4()
so i again call test2() from test4() with status flag but the problem is first request promise is overwritten in Memory stack.Is it possible to send request promise response from another function rather than that function.
var status = false;
(async () => {
var res = await test1();
console.log('Hi');
console.log(res);
})();
async function test1() {
console.log('Call to test2');
var restest1 = await test2();
console.log('All Finished', restest1);
return restest1;
}
function test2() {
return new Promise(async resolve => {
if (status) {
resolve('success');
}
else {
setTimeout(function () {
console.log('Call to test3');
test3();
}, 5000);
}
});
}
function test3() {
test4();
}
function test4() {
status = true;
test2();
console.log('test4 finished');
}
The problem is console.log('Hi'); console.log(res); not working
Based on your description and the desired output, I have a quick fix based on your current code structure. The idea is to pass the resolve function along until you really want to call it. So, when you call test2 inside test4 passing the initial resolve function, actually the original promise is resolved and returned. Does it answer your question or I miss your point?
(async () => {
var res = await test1();
console.log('Hi');
console.log(res);
})();
async function test1() {
console.log('Call to test2');
var restest1 = await test2();
console.log('All Finished', restest1);
return restest1;
}
function test2(resolve) {
return new Promise(async _resolve => {
if (resolve) {
resolve('success');
}
else {
setTimeout(function () {
console.log('Call to test3');
test3(_resolve);
}, 5000);
}
});
}
function test3(resolve) {
test4(resolve);
}
function test4(resolve) {
test2(resolve);
console.log('test4 finished');
}
var foo = function (callback_foo) {
async.series([func1, func2, func3], function (err) {
if (err) {
return callback_foo(err)
}
async.series([func4, func5], function(err){
if (err) {
return callback_foo(err)
}
return callback_foo(); //1
});
return callback_foo(); //2
});
}
do I need to return callback_foo() two times? the first callback_foo() is to tell async.series func4, fun5 is done. And the second callback_foo() is to tell the outer async.series func1,func2, func3 are done. is that right?
you could do it like below.
var foo = function (callback_foo) {
async.series([
func1(callback) {
//func1 processing
callback(); //this will call func2 after func1 is done
},
func2(callback) {
//func2 processing
callback(); //this will call func 3 after func 2 is done
},
func3(callback) {
//do your func3 processing here,then call async.series for 4 and 5.
async.series([
func4(callback) {
//do func 4 processing here
callback(); //This will call func5 after func4 is done
},
func5(callback) {
//do func5 processing here
callback(); //this will call the final callback of the nested async.series()
}], function (err) {
//this is the final callback of the nested(2nd) async.series call
callback(); //this is the iterator callback of func3,this will now call the final callback of the original async.series
});
}], function (err) {
//final callback after all the functions are executed.
return callback_foo();//call your foo's callback.
});
}
Note:callback used in func1,2,3,4,5 need not be defined,its async.series's iterator callback,thats helps us to move to the next function.
However i don't see the point of nested async.series calls.you could do it with 1 async.series call.
var foo = function (callback_foo) {
async.series([func1, func2, func3,func4,func5], function (err) {
if (err) {
return callback_foo(err)
}
});
};