Proper way to decorate async function [duplicate] - javascript

This question already has answers here:
How to measure the execution time of a promise?
(5 answers)
Closed 1 year ago.
I want to make a function decorator and measure async's function execution time, and return resolve or reject results as indented without decorating. Any clue how to achieve this? Most guides are about sync functions and are not touching the async world.
This is an example to illustrate the desired functionality
async exampleFunction () {
try{
const response = await aPromise() // The goals is to measure this one with a decorator measure(aPromise) and dispatch actions if the fetching takes a long time
}
catch (err){
// Do something with error
}

Here's a higher-order function that can measure the time it takes for the returned promise to settle either by fulfilling or rejecting:
function measure(fn) {
return async (...args) => {
const begin = performance.now();
try {
return await fn(...args);
} finally {
const end = performance.now();
console.log(`Execution time: ${end - begin}`);
}
};
}
Example usage:
exampleFunction();
async function exampleFunction() {
try {
const value = await measure(defer)('foo', 1000);
// where normally you'd write:
// const value = await defer('foo', 1000);
console.log(`Resolved with ${value}`);
} catch (reason) {
console.log(`Rejected with ${reason}`);
}
}
function measure(fn) {
return async (...args) => {
const begin = performance.now();
try {
return await fn(...args);
} finally {
const end = performance.now();
console.log(`Execution time: ${end - begin}`);
}
};
}
function defer(result, ms) {
return new Promise((resolve, reject) => {
setTimeout(() => {
const settle = Math.random() < 0.5 ? resolve : reject;
settle(result);
}, ms);
});
}

Something like this should do the trick:
export function MeasureExecutionTime(target, propertyKey, descriptor) {
const fun = descriptor.value;
descriptor.value = async function(...args) {
const start = performance.now();
const result = await fun.apply(target, args);
const end = performance.now();
console.log(`Execution time: ${end - start}`);
return result;
}
}

Related

Use Promise.all to expedite the excution of one asynced and one synced tasks - async await syntax

let's say I got three functions :
Asynchronous function - asyncF() - for example , it calls a REST API.
Synchronous function sync()
And executer function exec() which invokes them both as fast as possible , preferably in parallel .
I want exec() to be written using the "Async-await" syntax, without using the "Callbacks" or "Promises" syntax .
Does it make sense to write it as :
async exec(){
const [res1, res2]= await Promise.all([asyncF(), sync()])
...
}
Is there a better way?
const p = asyncF();
const syncResult = sync();
const asyncResult = await p;
Start the async task, then run the sync function, then wait for the promise to resolve.
If the sync task takes longer, there would be no extra wait async one, as the promise will have resolved:
function sync() {
const end = new Date();
end.setSeconds(end.getSeconds() + 3);
//loop for 3 seconds
for (let now = new Date(); now < end; now = new Date());
return 2;
}
function asyncF() {
//after 1 seconds return the value 40
return new Promise(resolve => setTimeout(resolve, 1000, 40));
}
async function exec(){
const p = asyncF();
const syncResult = sync();
const asyncResult = await p;
return syncResult + asyncResult;
}
const start = new Date();
exec()
.then(result => console.log(`result after ${new Date() - start}ms was: ${result}`));
If the async task takes longer, then there is an extra wait for the promise to resolve:
function sync() {
//return immediately
return 2;
}
function asyncF() {
//after 3 seconds return the value 40
return new Promise(resolve => setTimeout(resolve, 3000, 40));
}
async function exec(){
const p = asyncF();
const syncResult = sync();
const asyncResult = await p;
return syncResult + asyncResult;
}
const start = new Date();
exec()
.then(result => console.log(`result after ${new Date() - start}ms was: ${result}`))
This assumes, of course, that the async task is properly asynchronous. An async function will still be executed synchronously until it reaches an await or ends.
On errors
Do note that async rejections should still be handled if that is a risk. The problem here is that if sync() fails, then the await p would never be reached, thus a code like
const p = asyncF();
try {
const syncResult = sync();
const asyncResult = await p;
return syncResult + asyncResult;
} catch (error) { /* do something */}
will not properly handle the rejection coming from p.
For this we can take apage from Promise.allSettled() and safely wrap the promise results into an object with a status that tells whether it was fulfilled or rejected:
const p = asyncF()
.then(value => ({ status: "fulfilled", value }))
.catch(reason => ({ status: "rejected", reason });
This can further be encapsulated into small helpers:
const wrapPromise = p =>
p.then(
value => ({ status: "fulfilled", value }),
reason => ({ status: "rejected", reason })
);
const unwrapPromise = p =>
p.then((result) => {
const {status, value, reason} = result;
if (status === "fulfilled")
return value;
else if (status === "rejected")
throw reason;
throw new Error(`Unknown status ${status}, the value of the promise was ${result}`);
});
Using const bar = wrapPromise(foo) would prevent unhandled promise rejections by handling them and await unwrapPromise(bar) will then trigger the failure to happen at that point when it can be handled:
const p = wrapPromise(asyncF());
try {
const syncResult = sync();
const asyncResult = await unwrapPromise(p);
return syncResult + asyncResult;
} catch(error) { /* do something */ }
Thus if sync() fails first, there is no extra unhandled promise rejection going to happen later:
const wrapPromise = p =>
p.then(
value => ({ status: "fulfilled", value }),
reason => ({ status: "rejected", reason })
);
const unwrapPromise = p =>
p.then((result) => {
const {status, value, reason} = result;
if (status === "fulfilled")
return value;
else if (status === "rejected")
throw reason;
throw new Error(`Unknown status ${status}, the value of the promise was ${result}`);
});
function sync() {
const end = new Date();
end.setSeconds(end.getSeconds() + 1);
//loop for 1 seconds
for (let now = new Date(); now < end; now = new Date());
throw new Error("sync() failed");
}
function asyncF() {
//after 3 seconds reject with error the value 40
return new Promise((_, reject) => setTimeout(reject, 3000, new Error("asyncF() failed")));
}
async function exec(){
const p = wrapPromise(asyncF());
try {
const syncResult = sync();
const asyncResult = await unwrapPromise(p);
return syncResult + asyncResult;
} catch(error) {
throw new Error(`exec() failed because: ${error.message}`);
}
}
const start = new Date();
exec()
.then(result => console.log(`result after ${new Date() - start}ms was: ${result}`))
.catch(error => console.error(`error after ${new Date() - start}ms was: ${error.message}`));
While if sync() succeeds after asyncF() has failed, that is still OK, since the await unwrapPromise(p) will be the point where an exception will be raised:
const wrapPromise = p =>
p.then(
value => ({ status: "fulfilled", value }),
reason => ({ status: "rejected", reason })
);
const unwrapPromise = p =>
p.then((result) => {
const {status, value, reason} = result;
if (status === "fulfilled")
return value;
else if (status === "rejected")
throw reason;
throw new Error(`Unknown status ${status}, the value of the promise was ${result}`);
});
function sync() {
const end = new Date();
end.setSeconds(end.getSeconds() + 3);
//loop for 3 seconds
for (let now = new Date(); now < end; now = new Date());
return 2;
}
function asyncF() {
//after 1 seconds reject with error the value 40
return new Promise((_, reject) => setTimeout(reject, 1000, new Error("asyncF() failed")));
}
async function exec(){
const p = wrapPromise(asyncF());
try {
const syncResult = sync();
const asyncResult = await unwrapPromise(p);
return syncResult + asyncResult;
} catch(error) {
throw new Error(`exec() failed because: ${error.message}`);
}
}
const start = new Date();
exec()
.then(result => console.log(`result after ${new Date() - start}ms was: ${result}`))
.catch(error => console.error(`error after ${new Date() - start}ms was: ${error.message}`));
The async failure can be handled more simply if a fallback value is fine:
const p = asyncF().catch(error => {
//optionally do something with error
return -1; //fallback value
});
Then the await p will simply produce -1 on a failure.
You can't really run two things in parallel
And it also depends on how your async function work, here async function will block an entire thread for almost a second:
exec()
async function exec() {
const startTime = Date.now()
const [res1, res2] = await Promise.all([asyncF(), sync()])
console.log('finished', Date.now() - startTime, 'ms')
}
function asyncF() {
console.log('start async')
return Promise.resolve().then(() => {
let i = 100000000;
let x = 0
while (i--) {
x += Math.random()
}
return 'async'
})
}
function sync() {
console.log('start sync')
return 'sync'
}
About the question in the comment:
What Promise.all really do here is replace a map function
exec()
async function exec() {
const values = await Promise.all([sync(), sync(), sync()])
console.log(values)
const values2 = [sync, sync, sync].map(fn => fn())
console.log(values2)
}
function sync() {
return Math.random()
}
Is it better? No
Is it faster? Also no

How to debounce a function that return a Promise? [duplicate]

This question already has an answer here:
How to hold a NodeJS application until other promise completes
(1 answer)
Closed 9 months ago.
I have a base async function that returns a Promise
function asyncFoo(): Promise<void> {
return new Promise<void>((resolve, reject) => {
// I'm using here a setTimeout to fake async flow, in reality I'm making a server call
setTimeout(() => {
resolve();
}, 5000);
});
}
I can use my method
const promise = asyncFoo();
promise.then(() => {
console.log('Hello');
});
However if I debounce my function, the result function doesn't return anything, and I can't wait for it to resolve
const debounceFoo = _.debounce(asyncFoo, 100);
debounceFoo(); // undefined
// I would like to do something like
// debounceFoo().then(...) this will not work
How can I debounce the events (aggregate them) that happen during the debounce-time, then execute my asyncFoo(). And finally, act on its resolve callback?
You can use promise to do this:
const debounceAsync = (func, wait) => {
let timerID = -1;
return (...args) => {
clearTimeout(timerID);
const promiseForFunc = new Promise((resolve) => {
timerID = setTimeout(resolve, wait);
});
return promiseForFunc.then(() => func(...args));
};
};
const debounceFoo = debounceAsync(asyncFoo, 100);
debounceFoo().then(() => {
console.log('Hello');
});
The debounced function which return by lodash debounce usually return undefined before first time your func was invoked.
https://github.com/lodash/lodash/blob/2f79053d7bc7c9c9561a30dda202b3dcd2b72b90/debounce.js#L184-L206
Not sure about lodash's implementation but you can write your own debounce as this.
function debounce(func, timeout) {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => {
func.apply(this, args);
}, timeout);
};
}
function asyncBar() {
return new Promise((resolve) => {
setTimeout(() => {
console.log("Hi");
resolve();
}, 4000);
});
}
const foo = debounce(async () => asyncBar(), 5000);
foo();

Repeat async function until true

I have an async function that checks for the status of an order (checkOrderStatus()). I would like to repeat this function until it returns either "FILLED" or "CANCELED", then use this return value in another function to decide to continue or stop the code. Every order goes through different status before being "FILLED" or "CANCELED", therefore the need to repeat the checkOrderStatus() function (it is an API call).
What I have now is this, to repeat the checkOrderStatus() function:
const watch = filter => {
return new Promise(callback => {
const interval = setInterval(async () => {
if (!(await filter())) return;
clearInterval(interval);
callback();
}, 1000);
});
};
const watchFill = (asset, orderId) => {
return watch(async () => {
const { status } = await checkOrderStatus(asset, orderId);
console.log(`Order status: ${status}`);
if (status === 'CANCELED') return false;
return status === 'FILLED';
});
};
I then call watchFill() from another function, where I would like to check its return value (true or false) and continue the code if true or stop it if false:
const sellOrder = async (asset, orderId) => {
try {
const orderIsFilled = await watchFill(asset, orderId);
if (orderIsFilled) {
//… Continue the code (status === 'FILLED'), calling other async functions …
}
else {
//… Stop the code
return false;
}
}
catch (err) {
console.error('Err sellIfFilled() :', err);
}
};
However, this does not work. I can see the status being updated in the terminal via the console.log in watchFill(), but it never stops and most importantly, the value in the orderIsFilled variable in sellOrder() does not get updated, whatever the value returned by watchFill() becomes.
How can I achieve the desired behavior?
watch never calls resolve (in the original code, this is misleadingly named callback()) with any value, so there's no way const orderIsFilled = await watchFill(asset, orderId); will populate orderIsFilled with anything but undefined.
If you save the result of await filter() in a variable and pass it to
callback as callback(result), your code seems like it should work.
That said, the code can be simplified by using a loop and writing a simple wait function. This way, you can return a value (more natural than figuring out how/when to call resolve), keep the new Promise pattern away from the logic and avoid dealing with setInterval and the bookkeeping that goes with that.
const wait = ms =>
new Promise(resolve => setTimeout(resolve, ms))
;
const watch = async (predicate, ms) => {
for (;; await wait(ms)) {
const result = await predicate();
if (result) {
return result;
}
}
};
/* mock the API for demonstration purposes */
const checkOrderStatus = (() => {
let calls = 0;
return async () => ({
status: ++calls === 3 ? "FILLED" : false
});
})();
const watchFill = (asset, orderId) =>
watch(async () => {
const {status} = await checkOrderStatus();
console.log(`Order status: ${status}`);
return status === "CANCELLED" ? false : status === "FILLED";
}, 1000)
;
const sellOrder = async () => {
try {
const orderIsFilled = await watchFill();
console.log("orderIsFilled:", orderIsFilled);
}
catch (err) {
console.error('Err sellIfFilled() :', err);
}
};
sellOrder();
You can use recursive functionality like this:
const checkOrderStatus = async () => {
// ... function does some work ...
await someOtherFunction() // you can use here the other async function as well
// ... function does some more work after returning from await ...
if(/* if status is FILLED or CANCELED */) {
// return true or false or some info about response for your needs
} else {
checkOrderStatus();
}
}
// this will response back when status will be FILLED or CANCELED
await checkOrderStatus();
The watch function clears the interval timer after the first call if filter resolves with false. setInterval doesn't wait for an async function to finish executing either so you'll have to create a loop yourself. Try this:
const delay = milliseconds => new Promise(resolve => setTimeout(resolve, milliseconds));
const watch = async check => {
while (true) {
if (await check()) {
return;
}
await delay(1000);
}
};
Because watch only resolves when check succeeds, it is not possible to fail so you don't need to check for it (this might be a bug in your code):
const sellOrder = async (asset, orderId) => {
try {
await watchFill(asset, orderId);
//… Continue the code (status === 'FILLED'), calling other async functions …
}
catch (err) {
console.error('Err sellIfFilled() :', err);
}
};
p-wait-for contains an excellent implementation of this. You can use it like so:
import pWaitFor from 'p-wait-for';
const watchFill = (asset, orderId) => pWaitFor(async () => {
const { status } = await checkOrderStatus(asset, orderId);
console.log(`Order status: ${status}`);
if (status === 'CANCELED') return false;
return status === 'FILLED';
}, {
interval: 1000,
leadingCheck: false
});

jest.advanceTimersByTime doesn't work when I try to test my retry util function

I have a retry util function I wanted to test for. It looks like this
export const sleep = (t: number) => new Promise((r) => setTimeout(r, t));
type RetryFn = (
fn: Function,
config: {
retryIntervel: number;
retryTimeout: number;
predicate: Function;
onRetrySuccess?: Function;
onRetryFail?: Function;
}
) => Promise<any>;
export const retry: RetryFn = async (
fn,
{ predicate, onRetrySuccess, onRetryFail, retryIntervel, retryTimeout }
) => {
const startTime = Date.now();
let retryCount = 0;
while (Date.now() - startTime < retryTimeout) {
try {
const ret = await fn();
if (predicate(ret)) {
if (retryCount > 0) onRetrySuccess && onRetrySuccess();
return ret;
} else {
throw new Error();
}
} catch {
retryCount++;
}
await sleep(retryIntervel);
}
if (onRetryFail) onRetryFail();
};
what it does is retry the function for a period of time at a given interval.
I thought I could use jest.advanceTimersByTime to advance the timer to test how many times the retry happens.
import { retry } from "./index";
const value = Symbol("test");
function mockFnFactory(numFailure: number, fn: Function) {
let numCalls = 0;
return function () {
fn();
numCalls++;
if (numCalls <= numFailure) {
console.log("numCalls <= numFailure");
return Promise.resolve({ payload: null });
} else {
console.log("numCalls => numFailure");
return Promise.resolve({
payload: value
});
}
};
}
describe("retry function", () => {
beforeEach(() => {
jest.useFakeTimers();
});
it("retrys function on 1st attempt, and succeed thereafter", async () => {
const fn = jest.fn();
const onRetrySuccessFn = jest.fn();
const mockFn = mockFnFactory(3, fn);
retry(mockFn, {
predicate: (res: any) => res.payload === value,
onRetrySuccess: onRetrySuccessFn,
retryIntervel: 1000,
retryTimeout: 5 * 60 * 1000
});
jest.advanceTimersByTime(1000);
expect(fn).toHaveBeenCalledTimes(1);
expect(onRetrySuccessFn).not.toHaveBeenCalled();
jest.advanceTimersByTime(1000);
expect(fn).toHaveBeenCalledTimes(2); // 🚨 fail
expect(onRetrySuccessFn).not.toHaveBeenCalled();
jest.advanceTimersByTime(2000);
expect(fn).toHaveBeenCalledTimes(3);// 🚨 fail
expect(onRetrySuccessFn).toHaveBeenCalledTimes(1);
});
});
but it seems like no matter how much I advanced the timer, the function only gets invoked once.
You can find the code on codesandbox at https://codesandbox.io/s/lucid-knuth-e810e?file=/src/index.test.ts
However, there is a known issue with codesandbox where it keeps throwing this error TypeError: jest.advanceTimersByTime is not a function . This error doesn't appear locally.
It's because of this.
Here's what I use in a test helpers file:
const tick = () => new Promise(res => setImmediate(res));
export const advanceTimersByTime = async time => jest.advanceTimersByTime(time) && (await tick());
export const runOnlyPendingTimers = async () => jest.runOnlyPendingTimers() && (await tick());
export const runAllTimers = async () => jest.runAllTimers() && (await tick());
In my test file, I import my helpers and instead of calling jest.advanceTimersByTime, I await my advanceTimersByTime function.
In your specific example, you just need to await a function after calling advanceTimersByTime - like this:
// top of your test file
const tick = () => new Promise(res => setImmediate(res));
... the rest of your existing test file
jest.advanceTimersByTime(1000);
expect(fn).toHaveBeenCalledTimes(1);
expect(onRetrySuccessFn).not.toHaveBeenCalled();
jest.advanceTimersByTime(1000);
await tick(); // this line
expect(fn).toHaveBeenCalledTimes(2);
expect(onRetrySuccessFn).not.toHaveBeenCalled();
jest.advanceTimersByTime(2000);
await tick(); // this line
expect(fn).toHaveBeenCalledTimes(3)
expect(onRetrySuccessFn).toHaveBeenCalledTimes(1);
I am a little late but I had to solve this problem today and I solved it by making this new util function.
// So we can wait setTimeout loops
export const advanceTimersByNTimes = (n = 1, time = 1000) => {
for (let i = 0; i < n; i++) {
act(() => {
jest.advanceTimersByTime(time * 1);
});
}
};
and this is how I use this in the test:
await waitFor(() => {
expect(screen.queryByTestId("timeout-exceeded-container")).toBeNull();
});
advanceTimersByNTimes(11);
await waitFor(() => {
expect(screen.queryByTestId("timeout-exceeded-container")).not.toBeNull();
});
Nothing else worked for me, including the answers here.
This is the part in my code that doesn't work without the above hack (for reference):
setTimeout(() => setSeconds((prev) => prev + 1), 1000);
It would step to 2 times no matter what I've set jest.advanceTimersByTime to, those calls need to be wrapped in act blocks to count.

how do I create a nameless async function - Nodejs

I was wondering if it's possible to create a nameless function with the quick functional notation on javascript. What I mean by changing this:
var lobby = ((io) => {
...
}
return {
getWhiteBoardId: (name2, time2, role2, pass2, need_id) => {
let promise = new Promise((res, rej) => {
setTimeout(() => res("Now it's done!"), 1000)
});
// wait until the promise returns us a value
let result = await promise;
}
})(io);
I then want to later be able to call this function:
whiteboardId = lobby.getWhiteBoardId(req.body.name, req.body.time, req.body.role, req.body.pass, need_id);
to have something at the beginning such as this:
var lobby = (async (io) => {
so that I can call my Promise and await
// defining io, to execute the code, you not need to write the next line
const io = {};
const lobby = ((io) => {
// some pre-existing code
return {
// you are using await in this method so add async to the method signature
getWhiteBoardId: async (...args) => {
let promise = new Promise((resolve, reject) => {
setTimeout(() => resolve(args), 1000)
});
// wait until the promise returns us a value
let result = await promise;
// ... some other code/logic as needed
// i have assumed you need to return the result
return result;
}
}
})(io);
// TEST: calling getWhiteBoardID method
lobby.getWhiteBoardId("Hi", "There").then(console.log);
// or
(async () => {
const res = await lobby.getWhiteBoardId("Hello World")
console.log(res);
})();
console.log("First!");

Categories

Resources