Wait for executeScript to finish [duplicate] - javascript

This question already has answers here:
How do I convert an existing callback API to promises?
(24 answers)
Closed 1 year ago.
I have this script I execute from a Chrome extension
export const getData = () => {
chrome.tabs.query({ active: true, currentWindow: true }, tabs => {
chrome.tabs.executeScript(
tabs[0]?.id,
{ code: `document.getElementById('a1').innerHTML;` }, parseData
);
});
};
const parseData = (result: any) => {
const obj = new Something();
// Do complex stuff with this object
chrome.storage.sync.set(
{
id: obj.id,
}
);
console.log('ID from extract', obj.id);
};
From my background script, I call this function and then call the function that gets the parsed data through chrome storage, so both of these functions take callbacks as arguments.
I need my first function to finish before executing the other one but it's completely ignoring the await, I'm not sure why and how can I make it wait, if it's even possible
background script
chrome.runtime.onMessage.addListener(async(request, sender, sendResponse) => {
let list = [];
await getData();
await getExtractedData((value) => {
// Do a lot of stuff with `list`
console.log('After extract:', value);
});
}
);
getExtractedData function is in another file:
export const getExtractedData = (callback) => {
return chrome.storage.sync.get(
[
'id',
],
callback);
};
It's supposed print this:
ID from extract 123456
After extract {id: 123456}
But it's currently doing this:
After extract {id: 123455}
ID from extract 123456
(After extract is logging what it extracted in the previous run, but not in the current run)

Functions need to return a Promise with a properly wired resolve/reject.
const getData = () => new Promise(resolve => {
chrome.tabs.executeScript({code: `document.getElementById('a1').innerHTML`}, res => {
parseData(res).then(resolve);
});
});
const parseData = (data) => new Promise(resolve => {
// Do complex stuff with this object
chrome.storage.sync.set({id: obj.id}, () => resolve(obj.id));
});
const getExtractedData = () => new Promise(resolve => {
chrome.storage.sync.get('id', data => resolve(data.id));
});
BTW you don't need storage as getData's Promise chain already resolves to the processed id:
async foo => {
const resultId = await getData();
// ..........
}

Related

Function in JS that return defined data with 1 second delay

So,i have a task,to make a function 'mocker' which will return defined data with 1 second delay.The problem is that i'm new in JavaScript world and idk how to do this.I tried to take this way:
mocker = function mocker(data) {
var delayInMilliseconds = 1000;
setTimeout(function () {
return data;
}, delayInMilliseconds);};
But it doesnt satisfy the assignment.
I have this example :
const getUsers = mocker([{id: 1, name: 'User1'}]);
getUsers().then((users) => { // Will fire after 1 second.
console.log(users); // result: [{id: 1, name: 'User1'}];
});
And this is function description for test :
describe('mocker', () => {
describe('users mocker', () => {
const usersData = [{id: 1, login: 'mickey'}, {id: 2, login: 'billy77'}, {id: 3, login: 'coooool123'}];
let getUsers;
beforeEach(() => {
getUsers = mocker(usersData);
});
it('should return users data', async () => {
const resultData = await getUsers();
assert.deepStrictEqual(resultData, usersData);
});
it('should return users data in asynchronous way', () => {
const resultData = getUsers();
assert.notDeepStrictEqual(resultData, usersData);
});
});
});
#param data: {Array | Object}
#returns {Function}
Can you guys please help me? Thanks in advance.
The test suggests it's a little more complicated than just creating a function that returns some data after a second.
This line:
getUsers = mocker(usersData);
is saying that mocker needs to 1) accept some data and 2) return a function that we assign to getUsers. When we call getUsers it will then call that function to return the promise of data.
So when we get here:
const resultData = await getUsers();
we're calling that function we assigned to getUsers that promises to return some data (or not) after a second.
const usersData = [{id: 1, login: 'mickey'}, {id: 2, login: 'billy77'}, {id: 3, login: 'coooool123'}];
// `mocker` accepts some data and returns a
// function. This function is assigned to `getData`, and
// when `getData` is called this is the function that gets executed.
// It returns a promise that after one second data will be returned.
function mocker(usersData) {
return function () {
return new Promise((res, rej) => {
setTimeout(() => res(usersData), 1000);
});
}
}
// So we call `mocker` and assign the function it
// returns to `getUsers`
const getUsers = mocker(usersData);
async function main() {
// We can then call `getUsers` to get the data
// after one second has passed
console.log(await getUsers());
}
main();
Additional documentation
Promises
async/await

Trying to execute an imported Async function but the function is not behaving asynchronously

I am using React to build a website. I have imported an asynchronous function to execute when I press a button. However, the function is not working asynchronously and I really don't understand why.
interact.js:
export const getNFT = async () => {
setTimeout(() => {
console.log('getNFT code execute');
return nft;
}, 2000);
};
const nft = {
tokenURI: 'https://gateway.pinata.cloud/ipfs/QmdxQFWzBJmtSvrJXp75UNUaoVMDH49g43WsL1YEyb',
imageURL: 'https://gateway.pinata.cloud/ipfs/QmeMTHnqdfpUcRVJBRJ4GQ2XHU2ruVrdJqZhLz',
ID: '212'
};
Main.js
import {
getNFT
} from 'interact.js';
// This function is executed when a user clicks on a button
let getAllocatedNFT = async () => {
try {
let response = await getNFT();
console.log('response from server:: '+response);
}catch(e){
console.log(e);
}
};
console:
response from server:: undefined
getNFT code execute // This is executed correctly after 2 seconds
You have to return promise which will resolve your webAPI(setTimeout)
Please use like below:
const getNFT = async () => {
return new Promise(resolve => setTimeout(() => {
console.log("getNFT code execute")
resolve(true)
}, 2000)
);
};

Sequential Promise All call with a variable param

I have a function
this.config.apiFunc = (pageNo) => this.somePaginatedCall({
page: {
number: pNo,
size: 10
}
})
Now, I want to fetch the data in a batch of 5 pages (by maintaining the sequence). I added a delay of 2000ms for the sake of testing. I created
config = {
apiFunc: any,
data: []
}
async getData(){
const pageGroupList = [
[1,2,3,4],
[5,6,7,8]
];
const groupedPromise = [];
groupedPromise.push(this.pageGroupList.map(pageNo => this.config.apiFunc(pageNo))); //<-- This is making network request
// because I am trigerring the function call with ()
await this.asyncForEach(groupedPromise,this.fetchInBatch.bind(this));
}
private asyncForEach(promiseList, func): Promise<any> {
return promiseList.reduce((p,apiList) => {
return p.then(this.sleep(2000)).then(() => func(apiList));
}, Promise.resolve());
}
private fetchInBatch(apiList) {
return Promise.all(apiList).then((res: any) => {
// this gets called after every 2 secs but I do not see any call in Network tab
this.config.data = [...this.config.data , ...[].concat(...res.map(r => r.data))];
})
}
sleep(ms) {
return (x) => new Promise(resolve => setTimeout(() => resolve(x), ms))
}
The problem is that I am making API request at groupedPromise.push(this.pageGroupList.map(pageNo => this.config.apiFunc(pageNo))) which I should not.
The data although loads as expected (after 2000 ms delay) but the network calls are already made.
I want to load the data after the 1st batch of pages is loaded (1,2,3,4) . In this example, after 2 secs.
Problem is that I want to pass pageNo to each API call before I invoke the function. I am slightly confused.
Try to do it like below. I have moved the map function inside the Promise.all
async getData(){
const pageGroupList = [
[1,2,3,4],
[5,6,7,8]
];
this.asyncForEach(pageGroupList,this.fetchInBatch.bind(this));
}
private asyncForEach(pageGroupList, execFunc) {
return pageGroupList.reduce((p,pageGroup) => {
return p.then(() => execFunc(pageGroup));
}, Promise.resolve());
}
private fetchInBatch(pageGroupList) {
return Promise.all(pageGroupList.map(pageNo => this.config.apiFunc(pageNo))).then((res: any) => {
this.config.data = [...this.config.data, ...[].concat(...res.map(r => r.data))];
})
}
I think your problem is that you're mapping the results of calling, but you should mapping functions, try this instead:
//Returns [fn1, fn2, ...];
groupedPromise.push(...this.pageGroupList.map(pageNo => () => this.config.apiFunc(pageNo)));
Or better:
async getData(){
const pageGroupList = [
[1,2,3,4],
[5,6,7,8]
];
const groupedPromise = this.pageGroupList.map(pageNo => () =>
this.config.apiFunc(pageNo)));
await this.asyncForEach(groupedPromise,this.fetchInBatch.bind(this));
}

How to wait for the final result of a for loop with API calls?

This loop is going to run an arbitrary amount of times, and I want to get the result out of it after them all. Anything I try (promisifying, async/await, nesting functions, et al) seems to be a dead end. I don't get why I cannot just stick a .then on the API call, or on the function I made here. But I suspect the problem is more fundamental with my understanding, because I can't seem to even get just the "data" to return...same inability to wait on the API call. Wrapping it in a promise loses the "data" and pulling it with the "for loop" inside there doesn't work either. This is making me question my entire progress with JS/implementing other people's APIs.
const gotPeeps = () => {
challongeClient.tournaments.show({
id: tournamentURL,
callback: (err, data) => {
//return data //doesnt return "data" from gotPeeps?
for (const [key, value] of Object.entries(data.tournament.matches)) {
if (value.match.state === 'open') {
peepsList.push(value.match.player1Id, value.match.player2Id)
console.log(peepsList)
}}}})}
gotPeeps()
EDIT
To the comments:
I'm trying to get the results after the for loop is complete.
The "loop" I was referring to is the "for of" over the data Object.
"Putting the code after the loop but inside the callback" does not work. I have a previous question in my week of failing to solve this:
How to solve race condition in Javascript?
Here is the whole thing, with some past versions commented out.:
const tournamentModel = require('../models/tournamentSchema')
require('dotenv').config()
const challonge = require('challonge');
module.exports = {
name: 'getmatches',
aliases: ['gm'],
cooldown: 0,
description: 'Get Challonge data into console.',
execute(message, args, cmd, client, Discord, profileData) {
let peep = ''
let peepsList = ''
const tournamentURL = 'TESTING_Just_Sign_Up_If_You_See_This850587786533011496'
const challongeClient = challonge.createClient({
apiKey: process.env.CHALLONGE_API_KEY,
})
const getPlayer = (playerXId) => {
return new Promise((resolve, reject) => {
challongeClient.participants.show({
id: tournamentURL,
participantId: playerXId,
callback: (err, data) => {
if (err) {
reject(err);
return;
}
peep = data.participant.misc
peepsList.push(peep)
console.log(peepsList)
console.log('RUNNING GET PLAYER', playerXId, playerIndexCount)
resolve(peepsList);
}
});
});
}
const peepsList = []
const matchList = []
const gotPeeps = () => {
challongeClient.tournaments.show({
id: tournamentURL,
include_participants: 1,
include_matches: 1,
callback: (err, data) => {
for (const [key, value] of Object.entries(data.tournament.matches)) {
if (value.match.state === 'open') {
peepsList.push(value.match.player1Id, value.match.player2Id)
console.log(peepsList)
}
}
}
/*// GET PLAYERS
getPlayer(value.match.player1Id)
.then(() => {
getPlayer(value.match.player2Id)
})
}
*/
}
)
}
gotPeeps()
}}
You can make this function return a promise and await the function (as long as your function is an async function)
const gotPeeps = () => {
return new Promise((resolve, reject) => {
const peepsList = []; // declare the empty array to e filled
challongeClient.tournaments.show({
id: tournamentURL,
callback: (err, data) => {
for (const [key, value] of Object.entries(data.tournament.matches)) {
if (value.match.state === "open") {
peepsList.push(value.match.player1Id, value.match.player2Id);
}
}
resolve(peepsList); // resolve the promise with the filled array
// TODO: handle reject
},
});
})
};
(async () => {
try {
const result = await gotPeeps();
} catch (error) {
// TODO: handle error
}
})();

React typescript Promise with webservice and depending another function in loop

On button click I am looping through record id's pass that 1 by 1 to webservice which will return XML data where in I will get ID of another record which I will pass to another webservice which will return result success. After finish this I want to show message success
Function defination
const GetRunningWorkflowTasksForCurrentUserForListItemRequest = (
absoluteUrl: string,
itemId: number,
listName: string,
callback
) => {
const soapURL = `${absoluteUrl}/example.asmx?op=GetListItem`
const soapRequest = `SOAP Request`
getWFData(soapURL, soapRequest, callback) // Get XML with call back function parameter where we will process data
}
Second Function Call
const getWFData = (soapURL: string, soapRequest: string, callback) => {
const xmlhttp = new XMLHttpRequest()
xmlhttp.open("POST", soapURL, true)
xmlhttp.onreadystatechange = () => {
if (xmlhttp.readyState === 4) {
if (xmlhttp.status === 200) {
callback(xmlhttp.responseText)
}
}
}
xmlhttp.setRequestHeader("Content-Type", "text/xml")
xmlhttp.send(soapRequest)
}
First Function Call with loop
const approveSelected = (ids: number[]) => {
ids.forEach((val, idx) => {
const absoluteUrl = props.context.pageContext.web.absoluteUrl
// First function
GetRunningWorkflowTasksForCurrentUserForListItemRequest(
absoluteUrl,
val,
"Temp",
GetRunningWorkflowTasksForCurrentUserForListItemResponse //XML Response
)
})
}
Third Function where we got XML response
const GetRunningWorkflowTasksForCurrentUserForListItemResponse = (response: any) => {
const parser = require("fast-xml-parser")
const absoluteUrl = props.context.pageContext.web.absoluteUrl
if (parser.validate(response) === true) {
const jSONObj = parser.parse(response)
const spTaskId =
jSONObj["soap:Envelope"]["soap:Body"].GetRunningWorkflowTasksForCurrentUserForListItemResponse
.GetRunningWorkflowTasksForCurrentUserForListItemResult.UserTask.SharePointTaskId
processFlexiTaskRequest2(
absoluteUrl,
"Approve",
spTaskId,
"Workflow Tasks",
processFlexiTaskResponse2Response, //XML Response function
""
)
}
}
Forth and Final call for inside loop
const processFlexiTaskResponse2Response = (response: any) => {
const parser = require("fast-xml-parser")
if (parser.validate(response) === true) {
const jSONObj = parser.parse(response)
const result =
jSONObj["soap:Envelope"]["soap:Body"].ProcessFlexiTaskResponse2Response.ProcessFlexiTaskResponse2Result
}
}
I am really confuse, How can I make chain with promise and show confirm once loop finish. Please help
Two key steps are required to make this work with promises:
Convert each id to a promise that either resolves to the respective response or alternatively to the result of processFlexiTaskResponse2Response
Use Promise.all() to combine all these promises to one promise that resolves when all of the per-id promises are resolved.
This is the most relevant part
const approveSelected = (ids: number[]) => {
const promises = ids.map((val, idx) => {
const absoluteUrl = props.context.pageContext.web.absoluteUrl
// First function
return GetRunningWorkflowTasksForCurrentUserForListItemRequest(
absoluteUrl,
val,
"Temp"
).then(response => {
// transform response via
// GetRunningWorkflowTasksForCurrentUserForListItemResponse
// and
// processFlexiTaskResponse2Response
});
});
Promise.all(promises).then(results => {
// the code here executes once all results are there.
});
}
You have to change GetRunningWorkflowTasksForCurrentUserForListItemResponse and
processFlexiTaskResponse2Response to simply return their respective result so you can chain them.
To make GetRunningWorkflowTasksForCurrentUserForListItemRequest return a promise you can change it like this:
const GetRunningWorkflowTasksForCurrentUserForListItemRequest = (
absoluteUrl: string,
itemId: number,
listName: string,
callback
) => new Promise((resolve, reject) => {
const soapURL = `${absoluteUrl}/example.asmx?op=GetListItem`
const soapRequest = `SOAP Request`
getWFData(soapURL, soapRequest, resolve);
});

Categories