I have a function I want to execute in the page using chrome.tabs.executeScript, running from a browser action popup. The permissions are set up correctly and it works fine with a synchronous callback:
chrome.tabs.executeScript(
tab.id,
{ code: `(function() {
// Do lots of things
return true;
})()` },
r => console.log(r[0])); // Logs true
The problem is that the function I want to call goes through several callbacks, so I want to use async and await:
chrome.tabs.executeScript(
tab.id,
{ code: `(async function() {
// Do lots of things with await
return true;
})()` },
async r => {
console.log(r); // Logs array with single value [Object]
console.log(await r[0]); // Logs empty Object {}
});
The problem is that the callback result r. It should be an array of script results, so I expect r[0] to be a promise that resolves when the script finishes.
Promise syntax (using .then()) doesn't work either.
If I execute the exact same function in the page it returns a promise as expected and can be awaited.
Any idea what I'm doing wrong and is there any way around it?
The problem is that events and native objects are not directly available between the page and the extension. Essentially you get a serialised copy, something like you will if you do JSON.parse(JSON.stringify(obj)).
This means some native objects (for instance new Error or new Promise) will be emptied (become {}), events are lost and no implementation of promise can work across the boundary.
The solution is to use chrome.runtime.sendMessage to return the message in the script, and chrome.runtime.onMessage.addListener in popup.js to listen for it:
chrome.tabs.executeScript(
tab.id,
{ code: `(async function() {
// Do lots of things with await
let result = true;
chrome.runtime.sendMessage(result, function (response) {
console.log(response); // Logs 'true'
});
})()` },
async emptyPromise => {
// Create a promise that resolves when chrome.runtime.onMessage fires
const message = new Promise(resolve => {
const listener = request => {
chrome.runtime.onMessage.removeListener(listener);
resolve(request);
};
chrome.runtime.onMessage.addListener(listener);
});
const result = await message;
console.log(result); // Logs true
});
I've extended this into a function chrome.tabs.executeAsyncFunction (as part of chrome-extension-async, which 'promisifies' the whole API):
function setupDetails(action, id) {
// Wrap the async function in an await and a runtime.sendMessage with the result
// This should always call runtime.sendMessage, even if an error is thrown
const wrapAsyncSendMessage = action =>
`(async function () {
const result = { asyncFuncID: '${id}' };
try {
result.content = await (${action})();
}
catch(x) {
// Make an explicit copy of the Error properties
result.error = {
message: x.message,
arguments: x.arguments,
type: x.type,
name: x.name,
stack: x.stack
};
}
finally {
// Always call sendMessage, as without it this might loop forever
chrome.runtime.sendMessage(result);
}
})()`;
// Apply this wrapper to the code passed
let execArgs = {};
if (typeof action === 'function' || typeof action === 'string')
// Passed a function or string, wrap it directly
execArgs.code = wrapAsyncSendMessage(action);
else if (action.code) {
// Passed details object https://developer.chrome.com/extensions/tabs#method-executeScript
execArgs = action;
execArgs.code = wrapAsyncSendMessage(action.code);
}
else if (action.file)
throw new Error(`Cannot execute ${action.file}. File based execute scripts are not supported.`);
else
throw new Error(`Cannot execute ${JSON.stringify(action)}, it must be a function, string, or have a code property.`);
return execArgs;
}
function promisifyRuntimeMessage(id) {
// We don't have a reject because the finally in the script wrapper should ensure this always gets called.
return new Promise(resolve => {
const listener = request => {
// Check that the message sent is intended for this listener
if (request && request.asyncFuncID === id) {
// Remove this listener
chrome.runtime.onMessage.removeListener(listener);
resolve(request);
}
// Return false as we don't want to keep this channel open https://developer.chrome.com/extensions/runtime#event-onMessage
return false;
};
chrome.runtime.onMessage.addListener(listener);
});
}
chrome.tabs.executeAsyncFunction = async function (tab, action) {
// Generate a random 4-char key to avoid clashes if called multiple times
const id = Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1);
const details = setupDetails(action, id);
const message = promisifyRuntimeMessage(id);
// This will return a serialised promise, which will be broken
await chrome.tabs.executeScript(tab, details);
// Wait until we have the result message
const { content, error } = await message;
if (error)
throw new Error(`Error thrown in execution script: ${error.message}.
Stack: ${error.stack}`)
return content;
}
This executeAsyncFunction can then be called like this:
const result = await chrome.tabs.executeAsyncFunction(
tab.id,
// Async function to execute in the page
async function() {
// Do lots of things with await
return true;
});
This wraps the chrome.tabs.executeScript and chrome.runtime.onMessage.addListener, and wraps the script in a try-finally before calling chrome.runtime.sendMessage to resolve the promise.
Passing promises from page to content script doesn't work, the solution is to use chrome.runtime.sendMessage and to send only simple data between two worlds eg.:
function doSomethingOnPage(data) {
fetch(data.url).then(...).then(result => chrome.runtime.sendMessage(result));
}
let data = JSON.stringify(someHash);
chrome.tabs.executeScript(tab.id, { code: `(${doSomethingOnPage})(${data})` }, () => {
new Promise(resolve => {
chrome.runtime.onMessage.addListener(function listener(result) {
chrome.runtime.onMessage.removeListener(listener);
resolve(result);
});
}).then(result => {
// we have received result here.
// note: async/await are possible but not mandatory for this to work
logger.error(result);
}
});
For anyone who is reading this but using the new manifest version 3 (MV3), note that this should now be supported.
chrome.tabs.executeScript has been replaced by chrome.scripting.executeScript, and the docs explicitly state that "If the [injected] script evaluates to a promise, the browser will wait for the promise to settle and return the resulting value."
Related
React app can run node.js function which preparing data and sending information to the database in batches.
It takes a lot of time and I would like to add the ability to stop this function right from react app.
const getShopifyOrders = require('./shopify');
const getTrack = require('./tracking');
const Order = require('./model');
async function addOrdersToDB(limit) {
try {
// Get latest order from DB
let latestOrd = await Order.findOne().sort('-order_number');
do {
// Get Shopify Orders
let orders = await getShopifyOrders(
latestOrd ? latestOrd.order_id : 0,
limit
);
latestOrd = orders[0] ? orders[orders.length - 1] : undefined;
// Update array with tracking status
let fullArray = await getTrack(orders);
// Add to DB
let ins = await Order.insertMany(fullArray, { ordered: false });
console.log(`Added ${ins.length} entries`);
} while (latestOrd);
} catch (err) {
console.log(err);
}
}
module.exports = addOrdersToDB;
I tried a lot of things to include in this function including:
while loop: added the variable outside the function - if 'true' - run code, if not - return - it just doesn't work (variable was changed from react using socket.IO)
setTimeout (also setInterval), triger clearTimeout function from react: this doesn't work as setTimeout and setInterval doesn't work in async function
after that:
made (actually fond here on stackoverflow) new function to promisify setTimeout to be able to use in async function:
const setTimeout2 = (callback, ms) => {
return new Promise(
resolve =>
(to = setTimeout(() => {
callback();
resolve();
}, ms))
);
};
async function addOrdersToDB(limit) {
do {
await setTimeout2(async () => {
try {
// some code here
} catch (err) {
console.log(err);
}
}, 400);
} while (latestOrderExist);
}
function clearTO() {
setTimeout(() => {
console.log('clearTO');
clearTimeout(to);
}, 3000);
}
This for some reason doesn't iterate.
Is there solution for this?
Thanks!
To abort the do/while loop, you will need to add an additional test to that loop that is some variable that can be modified from the outside world. Also, note that the additional test only works here because you're using await inside the loop. If there was no await inside the loop, then the loop would be entirely synchronous and there would be no ability to change a variable from outside the loop while the loop was running (because of nodejs' single-threadedness).
Since this is a server (and globals are generally bad), I will assume we should not use a global. So instead, I would restructure addOrdersToDB() to return a data structure that contains both the promise the existing version returns and an abort() function the caller can call to stop the current processing. This also permits multiple separate calls to addOrdersToDB() to be running, each with their own separate abort() method.
function addOrdersToDB(limit) {
let stop = false;
function abort() {
stop = true;
}
async function run() {
try {
// Get latest order from DB
let latestOrd = await Order.findOne().sort('-order_number');
do {
// Get Shopify Orders
let orders = await getShopifyOrders(
latestOrd ? latestOrd.order_id : 0,
limit
);
latestOrd = orders[0] ? orders[orders.length - 1] : undefined;
// Update array with tracking status
let fullArray = await getTrack(orders);
// Add to DB
let ins = await Order.insertMany(fullArray, { ordered: false });
console.log(`Added ${ins.length} entries`);
} while (!stop && latestOrd);
// make resolved value be a boolean that indicates
// whether processing was stopped with more work still pending
return !!(latestOrd && stop);
} catch (err) {
// log error and rethrow so caller gets error propagation
console.log(err);
throw err;
}
}
return {
promise: run(),
abort: abort
}
}
So, to use this, you would have to change the way you call addOrdersToDB() (since it no longer returns just a promise) and you would have to capture the abort() function that it returns. Then, some other part of your code can call the abort() function and it will then flip the internal stop variable that will cause your do/while loop to stop any further iterations.
Note, this does not stop the asynchronous processing inside the current iteration of the do/while loop - it just stops any further iterations of the loop.
Note, I also changed your catch block so that it rethrows the error so that the caller will see if/when there was an error.
And, the resolved value of the function is the internal stop variable so the caller can see if the loop was aborted or not. A true resolved value means the loop was aborted and there was more work to do.
Here's an additional version of the function that creates more opportunities for it to stop between await operations within your function and within the loop. This still does not abort an individual database operation that may be in progress - you'd have to examine whether your database supports such an operation and, if so, how to use it.
function addOrdersToDB(limit) {
let stop = false;
function abort() {
stop = true;
}
async function run() {
try {
// Get latest order from DB
let latestOrd = await Order.findOne().sort('-order_number');
if (!stop) {
do {
// Get Shopify Orders
let orders = await getShopifyOrders(
latestOrd ? latestOrd.order_id : 0,
limit
);
latestOrd = orders[0] ? orders[orders.length - 1] : undefined;
if (stop) break;
// Update array with tracking status
let fullArray = await getTrack(orders);
if (stop) break;
// Add to DB
let ins = await Order.insertMany(fullArray, { ordered: false });
console.log(`Added ${ins.length} entries`);
} while (!stop && latestOrd);
}
// make resolved value be a boolean that indicates
// whether processing was stopped with more work still pending
return !!(latestOrd && stop);
} catch (err) {
// log and rethrow error so error gets propagated back to cller
console.log(err);
throw err;
}
}
return {
promise: run(),
abort: abort
}
}
I'm developing a Google Chrome extension which collects data from two servers and sends it to another service. I don't understand how to make it asynchronous. The requests seem to work fine.
I searched Google for some explanations but just found some basic tutorials with timeouts. Also, the Product-Server accepts the Ajax request, the Deal-Server doesn't (CORS Error). So I used XMLHttpRequest.
document.addEventListener("DOMContentLoaded", function () {
var createButton = document.getElementById("createButton");
createButton.addEventListener("click", function () {
getProducts();
getDeals();
}, false)
function getProducts() {
var list = [];
chrome.tabs.getSelected(null, function (tab) {
var Url = parseDealIdToUrl(tab.url)
$.get(Url, function (data, status) {
const jsonData = JSON.parse(JSON.stringify(data))
const productArray = jsonData.data
productArray.forEach(product => {
productList.push(new Product(product.id, product.deal_id, product.product_id, product.name, product.item_price, product.quantity, product.duration, product.discount_percentage, product.currency, product.sum, product.sum_formatted))
});
})
});
}
function getDeals(maxPages = 1, currentPage = 1, akquises = []) {
var akquiseList = akquises;
if (currentPage <= maxPages) {
var Url = dealUrl + currentPage
var xhr = new XMLHttpRequest();
xhr.open("GET", Url, true);
xhr.setRequestHeader("Authorization", "Token token=")
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
const akquiseArray = JSON.parse(xhr.responseText);
akquiseArray.forEach(akquise => {
akquiseList.push(new Akquise(akquise.name, akquise.id))
});
getDeals(handlePagination(xhr.getResponseHeader("Link")), currentPage + 1, akquiseList)
}
}
xhr.send();
}
}
}, false)
I want to call both functions and wait till both Lists are filled, then send the Data to the Service. Any idea would help me!
I'm not quite sure what you mean by "make it asynchronous." As wOxxOm said, XMLHttpRequest is an asynchronous method. Do you mean you're not sure how to combine the results of multiple asynchronous operations? For the sake of this answer I'll assume that's the case.
Basic Asynchronicity
In order to break down how asynchronous functions work, let's look at a simplified example of your code. Below we have a main function that calls 2 different asynchronous functions. When you run this block you'll get a DONE message logged to the console, Async 1 complete logged after 1 second, and Async 2 complete logged 1 more second later.
// Copyright 2019 Google LLC.
// SPDX-License-Identifier: Apache-2.0
(function main() {
doAsync1();
doAsync2();
console.log('DONE');
})()
function doAsync1() {
setTimeout(() => {
console.log('Async 1 complete');
}, 1000);
}
function doAsync2() {
setTimeout(() => {
console.log('Async 2 complete');
}, 2000)
}
The reason DONE is logged before the other statements is because doAsync1 and doAsync2 are asynchronous – it take a couple seconds for them to complete their work. When you call doAsync1() in main, the JS engine will step into the doAsync1 function and start executing lines. The first line is a setTimeout call. This function takes its first argument and schedules it for execution 1000 milliseconds later.
At this point the JS engine has done everything it can in doAsync1, so it steps out of that function and calls the next line, doAsync2. Again, doAsync2 schedules its callback for future execution and returns.
Next, the engine will execute the console.log line which makes DONE appear in the console.
1000 ms later, the callback scheduled by doAsync1 will run execute and log Async 1 complete to the console. Another 1000 ms later the callback scheduled by doAsync2 will log Async 2 complete.
Basic Callbacks
Now let's say doAsync1 and doAsync2 generate some data we want to use in main once both of complete. In JS, we traditionally use callbacks to get notified when some operation we're interested in completes.
// Copyright 2019 Google LLC.
// SPDX-License-Identifier: Apache-2.0
function doAsync1(callback) {
setTimeout(() => {
console.log('Async 1 started');
const data = "Async 1 payload";
callback(data);
}, 1000);
}
function doAsync2(callback) {
setTimeout(() => {
console.log('Async 2 started');
const data = "Async 2 payload";
callback(data);
}, 2000);
}
(function main() {
const response = {};
doAsync1(handleAsync1);
doAsync2(handleAsync2);
function handleAsync1(data) {
response.async1 = data;
handleComplete();
}
function handleAsync2(data) {
response.async2 = data;
handleComplete();
}
function handleComplete() {
if (response.hasOwnProperty('async1') && response.hasOwnProperty('async2')) {
console.log('DONE', response);
}
}
})();
Promises
While this does the job, it's a bit verbose. Promises are an abstraction of one-time callbacks that makes it easier to chain blocks of work together.
// Copyright 2019 Google LLC.
// SPDX-License-Identifier: Apache-2.0
// Promisified version of setTimeout
function timeout(duration) {
return new Promise(resolve => {
setTimeout(resolve, duration);
});
}
function doAsync1(callback) {
return timeout(1000).then(() => {
console.log('Async 1 started');
const data = "Async 1 payload";
return data;
});
}
function doAsync2(callback) {
return timeout(2000).then(() => {
console.log('Async 2 started');
const data = "Async 2 payload";
return data;
});
}
(function main() {
// Starts both doAsync1 and doAsync2 at the same time. Once both complete, the
// promise will resolve with both response values.
Promise.all([
doAsync1(),
doAsync2()
]).then(response => {
console.log('DONE', response[0], response[1]);
});
})();
Async/Await
With ES2016 we gained 2 new keywords: async and await. These keywords are essentially syntactic sugar that make it a little easier to work with promises in JavaScript. For demo purposes, let's take a look at our Promises example converted to async/await.
// Copyright 2019 Google LLC.
// SPDX-License-Identifier: Apache-2.0
function timeout(duration) {
return new Promise(resolve => {
setTimeout(resolve, duration);
});
}
async function doAsync1(callback) {
await timeout(1000);
console.log('Async 1 started');
const data = "Async 1 payload";
return data;
}
async function doAsync1(callback) {
await timeout(2000);
console.log('Async 2 started');
const data = "Async 2 payload";
return data;
}
(async function main() {
const response = await Promise.all([
doAsync1(),
doAsync2()
]);
console.log('DONE', response[0], response[1]);
})();
For a much deeper dive into async functions, check out Async functions - making promises friendly by Jake Archibald.
Use the following code snippet to add async/await functions to chrome extension.
Usage: put the following code snippet to the beginning of both your content script and background script.
/**
* Usage:
* let cookies = await asyncfy(chrome.cookies.getAll)({ url })
* let tabs = await asyncfy(chrome.tabs.query)({active: true, currentWindow: true})
*
* #param fn A function that takes one or more parameters, and the last parameter is a callback which has one or more parameter. The simplest one is chrome.management.getSelf
* #returns {function(...[*]): Promise<any>} Return one value if the results array has only one element, else return the whole results array
*/
let asyncfy = fn => (...args) => {
return new Promise((resolve, reject) => {
fn(...args, (...results) => {
let { lastError } = chrome.runtime
if (typeof lastError !== 'undefined') reject(lastError);
else results.length == 1 ? resolve(results[0]) : resolve(results);
});
});
}
let isObject = function(obj) {
var type = typeof obj;
return type === 'function' || type === 'object' && !!obj;
};
// provide async method to all methods which have one callback.
let handler = {
get: function(target, prop, receiver) {
let value = target[prop]
let type = typeof value
if(type !== 'undefined') { // including null, false
if( type === 'function') return value.bind(target); // correct the this for the functions, since we've substituted the original object to the proxy object
return value;
}
if(prop.endsWith('Async')){
let key = prop.replace(/Async$/, '')
let method=target[key]
let asyncMethod = asyncfy(method.bind(target));
return asyncMethod;
}
}
}
// proxy every leaf object
let asyncfyObj = handler => obj => Object.getOwnPropertyNames(obj)
.filter(prop => isObject(obj[prop]))
.forEach(prop => obj[prop] = new Proxy(obj[prop], handler))
// intercept the getters of all object in chrome member
asyncfyObj(handler)(chrome)
asyncfyObj(handler)(chrome.storage)
// console.log(`active tab: ${JSON.stringify(await getActiveTabAsync())}`)
let getActiveTabAsync = async () => {
let tabs = await chrome.tabs.queryAsync({active: true, currentWindow: true});
return (tabs && tabs.length > 0) ? tabs[0] : null;
}
// chrome.storage.local.set({ foo: 'bar' });
// console.log(`foo: ${await getLocalStorageAsync('foo')}`)
let getLocalStorageAsync = async key => ( await chrome.storage.local.getAsync([key]) ) [key];
Testing: put the following snippet in your background script and make sure related permissions have been added to the manifest.json.
(async () => {
console.log(cookies: ${JSON.stringify(await asyncfy(chrome.cookies.getAll)({ url: 'https://www.stackoverflow.com/' }))})
console.log(active tab: ${JSON.stringify(await getActiveTabAsync())})
chrome.storage.local.set({ 'foo': 'bar'});
console.log(storage: ${await getLocalStorageAsync('foo')})
console.log(extension install type: ${( await chrome.management.getSelfAsync() )['installType']})
} )()
my gist
I get some data from mongodb using mongoose find() and perform some validation on that data, but the problem is that this function is async and it does not wait for the data to be completed and execute the next code.
and when the next code is executed it enables to perform because it has null data. then i wrote my validation logic in the async function so that when data is available only then it move to next code but on every return it sends undefined data.
function isValidObject(obj) {
schemaData.find({}, (error, data) => { // calls data from db
var contactSchema = data; // this is data i need
if(//validation fails){
return "wrong data";// every time it returns undrfined on every
// condition
}
});
}
var validationResp = isValidObject(obj);
console.log(validationResp); // output is undefined
i also used "await" to wait for the data, but in that case it return [object promise] on every return statement
use async/await
In your case:
async function isValidObject(obj) {
let data = await schemaData.find({}); // wait until it resolve promise
//do your validaion
return data;
}
isValidObject(obj).then((validationResp)=>{console.log(validationResp)});
use the then() method it return a promise
var promise1 = new Promise(function(resolve, reject) {
resolve('Success!');
});
promise1.then(function(value) {
console.log(value);
// expected output: "Success!"
});
more details at MDN Using the then method
Are you familiar with "promises".You can use ".then(callback)". It will wait until async function is executed. And you can perform your validation in the callback function
User.findById(id)
.then(function(user) {
if (!user) {
return "here you return an error";
}
return "you return OK with payload from database";
})
.catch(next);
What is happening in your case is that when you assign var validationResp = isValidObject(obj); the function isValidObject(obj) has not returned anything and has only scheduled a callback(This is a very important concept when working with callbacks). As pointed out above you need to use Promises. The below is an example of your case as to how you can use Promises.
function isValidObject(obj){
return new Promise((resolve,reject) => {
schemaData.find({}, (error, data) => { // calls data from db
if(validation fails){
reject(error)// every time it returns undrfined on every
// condition
}else{
var contactSchema = data; // this is data i need
resolve(data)// You can resolve the promise with the data directly instead
// of assigning it to a variable or you can use (resolve(contactSchema))
}
})
})
}
After this when you want to use the data you can use do something like the below code snippet.
isValidObject(obj)
.then(result => {
// Use the result object here. This is the result from the Database
})
.catch(error => {
//Handle the Error
})
So I'm using puppeteer in nodeJS and I have a function that recursively calls itself until a specified page is correctly loaded:
const isPageAccessible = async (page, url) => {
var pageRes = await page.goto(url, {
timeout: 10000
}).catch(e => console.log(e));
if (!pageRes || pageRes.status !== 200) {
console.log("Website didn't load properly.")
isPageAccessible(page, url);
} else {
console.log("Loaded!")
return true;
}
}
The problem here is that this function is returning undefined after the first recursive call (which is normal as far as I know since async functions must resolve with a value). I want the code to wait until this function resolves with true
console.log(await isPageAccessible(page,LOGIN_URL));
console.log("Done!")
So the console would log "Done!" after the website has been loaded succesfully. Right now it is logging "Done!" even if the website hasn't been loaded since isPageAccessible function is returning undefined after the first call.
Any thoughts on how to solve this would be appreciated!
You need to return the recursive call up the Promise chain:
if (!pageRes || pageRes.status !== 200) {
console.log("Website didn't load properly.")
return isPageAccessible(page, url);
}
Instead use this npm module,
https://www.npmjs.com/package/sync-request
var request = require('sync-request');
var res = request('GET', 'http://example.com');
console.log(res.getBody());
console.log("Done!")
All the statements will be executed one after another after completion.
Lesser code i guess!!
Using ES6, Classes, and Aync/Await...
The goal is to have an "Api" class that does async calls with fetch, and returns some data.... but even the basic foundation isn't working.
in the main js these snippet runs, which starts the chain of events:
let api = new Api();
let api_data = api.getLocation();
console.log(api_data);
getLocation method is the following, which would return some response/data. However, that data would theoretically be a fetch call to an API, which is "getTestVariable" for example that waits some time...
class Api {
getLocation = async () => {
var response = await this.getTestVariale();
console.log(response);
return response;
}
getTestVariale = () =>{
setTimeout(function(){
console.log("timeout done...");
return "this is the test variable";
},2000);
}
}
However, 1) the console.log(response) gives "undefined" because its not awaiting... and 2) back in the main js, api_data when logged, is some Promise object rather than the variable response
As the comment above states, setTimeout does not return a Promise, so getTestVariable has no return value.
Here's a slightly modified version of your code that will hopefully put you on the right track:
class Api {
getLocation = async () => {
var response = await this.getTestVariale();
console.log(response);
return response;
}
getTestVariale = () => {
return new Promise((resolve, reject) => {
if (thereIsError)
reject(Error('Error message'));
else
resolve({foo: 'bar'});
}
}
}
Drop a comment if I need to explain further I'd be happy to.
In getLocation you await for a value that will come from this.getTestVariable. In order for this to work this.getTestVariable must return a Promise; it can be done in two ways - making getTestVariable an async function or explicitly returning a Promise.
Since you're using setTimeout (which is not an async function) you're bound to use Promise. Here you go:
class Api {
async getLocation() {
return await this.getTestVariable();
}
getTestVariable() {
return new Promise((res, rej) => {
setTimeout(() => res('test'), 2000)
});
}
}
async function main() {
let api = new Api;
console.log('Trying to get the location...');
console.log('Voila, here it is: ', await api.getLocation());
}
main();
Looks quite ugly but there's no way you can achieve it if you use set timeout.
The main point is in resolution of getTestVariable with value you want it to return.
Quite an important remark: you can mark getTestVariable as an async function, it will ad an extra Promise level, but you still will have the desired result.