simulate async with array and callback in javascript - javascript

if I had an array and a callback function, how do I get the results back asynchronously? here's what I've been trying
const fakeAsync = (list = [], cb = '') => {
let map = {};
list.forEach((x) => {
map[x] = cb(x)
})
return map;
}
const list = [
'user1',
'user2',
'user3'
]
const cb = (name) => {
setTimeout(function(){ return 'good ' + name }, 3000);
}
fakeAsync(list, cb)
it prints out
=> { user1: undefined, user2: undefined, user3: undefined }
I want it to be
=> { user1: 'good user1', user2: 'good user2', user3: 'good user3' }

Calling cb will return nothing directly, so the return inside the setTimeout doesn't actually return anything to be put inside the map object.
You could use Promises to get it simulated like this:
const fakeAsync = (list = [], cb = ()=>{}) => {
Promise.all( // Wait for all Promises to settle/resolve
list.map((x) => { // Make an array of Promises from the list
return new Promise((resolve) => { // Return Promise to be settled
setTimeout(() => { // Simulated delay before resolving with a return value
return resolve(cb(x))
}, 3000)
})
})
).then((map) => { // Get all returned values from the Promises
console.log(map);
});
}
const list = [
'user1',
'user2',
'user3'
]
const cb = (name) => {
return 'good ' + name; // The actual return value
}
fakeAsync(list, cb);
Some references/documentation:
https://developer.mozilla.org/nl/docs/Web/JavaScript/Reference/Global_Objects/Promise
https://developers.google.com/web/fundamentals/primers/promises

Related

How can I get a timeOut to work inside the GraphQL root object?

In my GraphQL backend, the "employees" array is fetched and returned correctly even though it has to await its data, but the "slowEmployees" which contains a timeOut returns an empty array. How can I get "slowEmployees" to wait to return its data?
const root = {
hello: () => {
return 'hello world';
},
message: () => {
return 'the message';
},
books: () => {
return ['More About Linux', 'Bash Shell Scripting'];
},
employees: async () => {
const rawEmployees = (
await axios.get(
'https://edwardtanguay.netlify.app/share/employees.json'
)
).data;
const employees = [];
rawEmployees.forEach((rawEmployee) => {
const employee = {
firstName: rawEmployee.firstName,
lastName: rawEmployee.lastName,
};
employees.push(employee);
});
return employees;
},
slowEmployees: () => {
const employees = [];
setTimeout(async () => {
const rawEmployees = (
await axios.get(
'https://edwardtanguay.netlify.app/share/employees.json'
)
).data;
rawEmployees.forEach((rawEmployee) => {
const employee = {
firstName: rawEmployee.firstName,
lastName: rawEmployee.lastName,
};
employees.push(employee);
});
}, 2000);
return employees;
},
};
You need to make setTimeout compatible with async/await - a very common problem.
If you create a "Promisified" setTimeout:
function timeout(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
... you can await it similarly to your regular employees endpoint:
...
slowEmployees: async () => {
// Fetch raw data
const rawEmployees = (
await axios.get(
'https://edwardtanguay.netlify.app/share/employees.json'
)
).data;
// Simulate being slow
await timeout(2000);
// Transform to required data shape and return
return rawEmployees.map((rawEmployee) => {
return {
firstName: rawEmployee.firstName,
lastName: rawEmployee.lastName,
};
});
},
Note I also changed your .forEach() + .push combination to a .map() - you're performing the transformation on each array element, with no side-effects, so it's a better semantic fit.

How to use the result of first promise in the second and so on in Promise.all

By following a good answer by T.J. Crowder to a SO Thread, I managed to combine a loop of async tasks with Promise.all. The actual problem is, first I want to read one excel file in a Promisified function and a list of image files in the second Promisified function.
Here is the code functions performing files reading.
import { User } from "./types";
import * as XLSX from "xlsx";
// Loading users data from Excel Data... Id,Name,CardNo
export async function loadUsersData(usersFile: File) {
let result_users: User[] =await new Promise((resolve) => {
var reader = new FileReader();
reader.onload = function (e) {
const data = e.target.result;
const readedData = XLSX.read(data, { type: 'binary' });
const wsname = readedData.SheetNames[0];
const ws = readedData.Sheets[wsname];
/* Convert array to json*/
const parsedData = XLSX.utils.sheet_to_json(ws, { header: 1, blankrows: false });
parsedData.shift();
const users: User[] = parsedData.map((item: any) => {
const id = item[0].toString().trim();
const name = item[1].toString().trim();
const cardNo = item[2].toString().trim();
const user: User = { id, name, cardNo };
return user;
});
resolve(users);
}
reader.readAsBinaryString(usersFile)
});
return result_users;
}
//Loading Images of Users Faces to display in material table along with other user info
export async function loadUsersFaces(users: User[], facesList: FileList) {
const facesArray = Array.from(facesList)
const promises=facesArray.map(async face=>{
return await readFace(face, users);
})
let result_users: any=await Promise.all(promises);
return result_users
}
function readFace(face: File,users:User[]) {
return new Promise((resolve) => {
const reader = new FileReader();
reader.onload = function (e) {
let faceBase64String = e.target.result; //getting Base64String of image to render as custom column in material-table as https://material-table.com/#/docs/features/custom-column-rendering
users.map(user => {
if (face.name.includes(user.id) && face.name.includes(user.name)) {
let newUser={ ...user, face: faceBase64String };
console.log(`Resoling ${JSON.stringify(newUser)}`);
resolve(newUser);
}
})
}
reader.readAsDataURL(face)
});
}
And here is the code of Actions performing files reading one after the other.
//Here is usersFile is an excel file Blob and FileList contain list of image files
export const loadUsers = (usersFile: File,faces: FileList) => (dispatch:Dispatch) => {
dispatch(actions.startCall({ callType: callTypes.list }));
usersService.loadUsersData(usersFile).then((users:any)=>{ // Don't know how to tell compiler that it's User[]
usersService.loadUsersFaces(users,faces).then((users:any)=>{
console.log(users); // Here I should have users including Base64 Strings of face images in face property
dispatch(actions.usersFetched({ totalCount:users.length, entities:users }));
})
})
};
My answer to this other question comes close to answering this, but I'm not sure it completely does.
Since you want to use the first operation's result in the second, and the second operation's result in the third, etc., you can't run the asynchronous actions in parallel. So you have to run them in series.
If you can use an async function (well supported these days), you'd do that like something this:
async function doSeriesOfThings() {
let lastResult = /* the first value to pass, perhaps `undefined` or `null` */;
for (const obj of arrayofObjs) {
lastResult = await doSomeAsyncStuff(obj, lastResult);
}
return lastResult;
}
Live Example:
const arrayofObjs = [
{value: 1},
{value: 2},
{value: 3},
];
function doSomeAsyncStuff(obj, value) {
console.log(`doSomeAsyncStuff(${JSON.stringify(obj)}, ${value})`);
return new Promise(resolve => {
setTimeout(() => {
resolve(obj.value + value);
}, Math.random() * 500);
});
}
async function doSeriesOfThings() {
let lastResult = 0;
for (const obj of arrayofObjs) {
lastResult = await doSomeAsyncStuff(obj, lastResult);
}
return lastResult;
}
doSeriesOfThings()
.then(result => console.log(`Final result: ${result}`))
.catch(error => console.error(`Error: ${error.message || String(error)}`));
If you also need an array of results, just build it up in the function:
async function doSeriesOfThings() {
const results = [];
let lastResult = /* the first value to pass, perhaps `undefined` or `null` */;
for (const obj of arrayofObjs) {
lastResult = await doSomeAsyncStuff(obj, lastResult)
results.push(lastResult);
}
return results;
}
Live Example:
const arrayofObjs = [
{value: 1},
{value: 2},
{value: 3},
];
function doSomeAsyncStuff(obj, value) {
console.log(`doSomeAsyncStuff(${JSON.stringify(obj)}, ${value})`);
return new Promise(resolve => {
setTimeout(() => {
resolve(obj.value + value);
}, Math.random() * 500);
});
}
async function doSeriesOfThings() {
const results = [];
let lastResult = 0;
for (const obj of arrayofObjs) {
lastResult = await doSomeAsyncStuff(obj, lastResult)
results.push(lastResult);
}
return results;
}
doSeriesOfThings()
.then(result => console.log(`Final result: ${JSON.stringify(result)}`))
.catch(error => console.error(`Error: ${error.message || String(error)}`));
If you can't use an async function, it's fairly similar, but you build up a promise chain:
function doSeriesOfThings() {
let promise = Promise.resolve(/* the first value to pass, perhaps `undefined` or `null` */);
for (const obj of arrayofObjs) {
promise = promise.then(result => doSomeAsyncStuff(obj, result));
// Or you can write it like this: `promise = promise.then(doSomeAsyncStuff);`
}
return promise;
}
Live Example:
const arrayofObjs = [
{value: 1},
{value: 2},
{value: 3},
];
function doSomeAsyncStuff(obj, value) {
console.log(`doSomeAsyncStuff(${JSON.stringify(obj)}, ${value})`);
return new Promise(resolve => {
setTimeout(() => {
resolve(obj.value + value);
}, Math.random() * 500);
});
}
function doSeriesOfThings() {
let promise = Promise.resolve(0);
for (const obj of arrayofObjs) {
promise = promise.then(result => doSomeAsyncStuff(obj, result));
// Or you can write it like this: `promise = promise.then(doSomeAsyncStuff);`
}
return promise;
}
doSeriesOfThings()
.then(result => console.log(`Final result: ${result}`))
.catch(error => console.error(`Error: ${error.message || String(error)}`));
And again, if you need an array of results, you can do that too:
function doSeriesOfThings() {
const results = [];
let promise = Promise.resolve(/* the first value to pass, perhaps `undefined` or `null` */);
for (const obj of arrayofObjs) {
promise = promise.then(result => doSomeAsyncStuff(obj, result).then(result => {
results.push(result);
return result;
}));
// Or you can write it like this: `promise = promise.then(doSomeAsyncStuff);`
}
return promise.then(() => results);
}
Live Example:
const arrayofObjs = [
{value: 1},
{value: 2},
{value: 3},
];
function doSomeAsyncStuff(obj, value) {
console.log(`doSomeAsyncStuff(${JSON.stringify(obj)}, ${value})`);
return new Promise(resolve => {
setTimeout(() => {
resolve(obj.value + value);
}, Math.random() * 500);
});
}
function doSeriesOfThings() {
const results = [];
let promise = Promise.resolve(0);
for (const obj of arrayofObjs) {
promise = promise.then(result => doSomeAsyncStuff(obj, result).then(result => {
results.push(result);
return result;
}));
// Or you can write it like this: `promise = promise.then(doSomeAsyncStuff);`
}
return promise.then(() => results);
}
doSeriesOfThings()
.then(result => console.log(`Final result: ${JSON.stringify(result)}`))
.catch(error => console.error(`Error: ${error.message || String(error)}`));
If you want to use loops to chain promises you will need async & await
async function chainPromiseNTimes(function_returning_promise,n,data){
for(let i=0;i<n;i++) {
data = await function_returning_promise(data)
// data (modified) will be passed to new promise in next iteration
}
return data;
}
let result = await chainPromiseNTimes(doSomeAsyncStuff, 5, arrayofObjs)
You could try pushing functions returning promises instead of promises onto your array. This way you could simply call them when the data from the last promise is actually available.
function doSomeAsyncStuff(arrayofObjs) {
// this is not rly asynchronous but for the purpose of example will do
return new Promise(function(resolve) {
const result = arrayofObjs.map(obj => ++obj);
resolve(result);
});
}
async function waitForPromiseChain(initialData, functionCallbacks) {
let temp = initialData;
for (let i = 0, l = functionCallbacks.length; i < l; i++)
temp = await functionCallbacks[i](temp);
return temp;
}
const promises = [];
for (i = 0; i < 5; i++) {
promises.push((arrayofObjs) => doSomeAsyncStuff(arrayofObjs));
}
waitForPromiseChain([0, 0, 0, 0, 0], promises)
.then(console.log);
In the example above I tried to keep code as close to your original as possible. However i took the liberty of redesigning function callbacks to accept any function in a chain instead of a single one.
If you are opposed to using async/await the same effect can be achieved with usage of normal then, even if with some difficulty.
function doSomeAsyncStuff(arrayofObjs) {
// this is not rly asynchronous but for the purpose of example will do
return new Promise(function(resolve) {
const result = arrayofObjs.map(obj => ++obj);
resolve(result);
});
}
function waitForPromiseChain(initialData, functionCallbacks) {
let temp = Promise.resolve(initialData);
for (let i = 0, l = functionCallbacks.length; i < l; i++)
temp = temp.then(data => functionCallbacks[i](data));
return temp;
}
const promises = [];
for (i = 0; i < 5; i++) {
promises.push((arrayofObjs) => doSomeAsyncStuff(arrayofObjs));
}
waitForPromiseChain([0, 0, 0, 0, 0], promises)
.then(console.log);

Returning data out from promise then chain

I have a class method (for 'apollo-datasource-rest' that is supposed to fetch a plan from a plan_id. I need to hit two endpoints and combine the data. This works with Promise.all([promise1, promise2]) and then passes the user_ids on to the next Promise.all method that calls the GET /users endpoint multiple times. If I console.log out the usersArray that is returned, I get the array of users, but if I try to return that array, it doesn't get assigned to the objToReturn variable. I also need to add data from snapshot2 to the objToReturn but that is secondary.
getPlanById = async ( planId ) => {
const promise1 = new Promise((resolve) => {
return resolve(this.get('/url1'))
});
const promise2 = new Promise((resolve) => {
return resolve(this.get('/url2'))
});
const objToReturn = await Promise.all([promise1, promise2])
.then(([snapshot1, snapshot2]) => {
return snapshot1.user_ids
})
.then((userIds) => {
this.getUsersFromUserIds(userIds).then((usersArray) => {
console.log(usersArray)
// return usersArray doesn't assign to objToReturn
})
})
return objToReturn
}
getUsersFromUserIds(userIds) {
let userPromises = []
userIds.forEach((uid) => {
const promise = this.get(`/users/${uid}`)
.then((response) => {
if (response.status === 'success') {
return response.data.user
} else {
return null
}
})
userPromises.push(promise)
})
return Promise.all(userPromises).
then((userPromiseData) => {
return userPromiseData
})
}
You need to return the promise of this.getUsersFromUserIds(userIds). So that the promise chain can work.
E.g.
index.js:
class UserAPI {
async getPlanById(planId) {
const promise1 = Promise.resolve({ user_ids: [1, 2] });
const promise2 = Promise.resolve();
const objToReturn = await Promise.all([promise1, promise2])
.then(([snapshot1, snapshot2]) => {
return snapshot1.user_ids;
})
.then((userIds) => {
// You need to return here
return this.getUsersFromUserIds(userIds).then((usersArray) => {
return usersArray;
});
});
return objToReturn;
}
async getUsersFromUserIds(userIds) {
return [
{ id: 1, name: 'a' },
{ id: 2, name: 'b' },
];
}
}
const userAPI = new UserAPI();
userAPI.getPlanById(1).then((objToReturn) => {
console.log('objToReturn: ', objToReturn);
});
The output in the console:
objToReturn: [ { id: 1, name: 'a' }, { id: 2, name: 'b' } ]

Await inside loop before move to another iteration

I am trying to send message through an API using a function. When function do his duty it returns back a value which is messageLodId and it needs to be updated at Attendence in main loop. But when I execute this code, value comes undefined.
There are two questions:
1) Is the structure right?
2) If yes, please provide the answer for this problem.
//Posting SMS
router.post('/sms/', async function(req, res) {
let attendenceRecordId = parseInt(req.body.attendenceRecordId);
let result = await AttendenceRecord.findOne({where: {id: attendenceRecordId }, include: [ {model: Attendence, include: [{model: Student}
]}, {
model: Class
}], order: [['date', 'DESC']]});
if(!result) {
res.sendStatus(404);
}
for await (let attendence of result.attendences){
let messageLogId = await sendSMS(attendence);
console.log("Message ID: ", messageLogId);
Attendence.update(
{ smsLogId: messageLogId },
{ where: { id: attendence.id } }
);
}
AttendenceRecord.update(
{ isMessageSent:true },
{ where: { id: result.id } }
);
res.send({messageSent: true});
});
Here is the function definition. Right now I am just returning 1.
In real world the URL returns a code.
async function sendSMS(attendence){
//console.log(target);
setTimeout(function(){
let message = `Respected Parent, your son/daughter ${attendence.student.name} is absent on ${attendence.date}`;
let messageURL = encodeURI(message);
let api = 'SOME VALUE';
let phone = attendence.student.fphone.substring(1, 11);
let target = `http://BASE_URL/api.php?key=${api}&receiver=92${phone}&sender=DigitalPGS&msgdata=${messageURL}`;
return 1;
}, 2000);
}
You should return promise from sendSMS. Resolve promise in setTimeout callback function.
function sendSMS(attendence){
//console.log(target);
return new Promise((resolve, reject) => {
setTimeout(function(){
let message = `Respected Parent, your son/daughter ${attendence.student.name} is absent on ${attendence.date}`;
let messageURL = encodeURI(message);
let api = 'SOME VALUE';
let phone = attendence.student.fphone.substring(1, 11);
let target = `http://BASE_URL/api.php?key=${api}&receiver=92${phone}&sender=DigitalPGS&msgdata=${messageURL}`;
resolve(1);
}, 2000);
});
}
You should have sendSMS return a promise, and await that:
exec();
async function exec()
{
var records = [1,2,3];
for(var i=0;i<records.length;i++)
{
var messageLogId = await sendSMS(records[i]);
console.log("Result received from resolve", messageLogId);
}
}
function sendSMS(record)
{
// simulate an async method:
return new Promise(function(resolve, reject) {
setTimeout(function() {
console.log("Send sms for record", record);
resolve(1);
}, 1000);
});
}
Note that the setTimeout here is just to demonstrate an asynchronous action. In the real world your sendSMS function will no doubt call an API, which will itself be asynchronous - just return the promise from that (or, wrap the call in a promise if the API client doesn't return one).
First, make your function Promisify. Then chunk your function and call that function in the for loop and handle with Promise.all().
const manyPromises = [];
for (const attendence of result.attendences) {
manyPromises.push(sendSmsAndUpdateStatus(attendence));
}
// Execution wait until all promises fulfilled/rejected
const result = await Promise.all(manyPromises);
const sendSmsAndUpdateStatus = async (attendence) => {
try {
const messageLogId = await sendSMS(attendence);
const updateObj = { smsLogId: messageLogId };
const condition = { where: { id: attendence.id } };
const result = await Attendence.update(updateObj, condition);
return { result, messageLogId };
} catch (err) {
logger.error(err);
throw err;
}
};
const sendSMS = (attendence) => {
return new Promise((resolve) => {
setTimeout(() => {
const message = `Respected Parent, your son/daughter ${attendence.student.name} is absent on ${attendence.date}`;
const messageURL = encodeURI(message);
const api = 'SOME VALUE';
const phone = attendence.student.fphone.substring(1, 11);
const target = `http://BASE_URL/api.php?key=${api}&receiver=92${phone}&sender=DigitalPGS&msgdata=${messageURL}`;
return resolve(1);
}, 2000);
});
};
Summary make sure your function sendSMS will return Promise, afterwards you can handle it with async/await or .then().catch() approaches.

How to return promise?

I have a function
parseJobs(userId: string) {
this.getLikedJobs(userId).subscribe(result => {
result.map(key =>{
let rows = {
name : (key as any).jobsUser.firstName,
jobType: 'Liked'
}
let job = {...rows,...(key as any).jobPosting};
this.result.push(job);
});
});
this.getSavedJobs(userId).subscribe(result => {
result.map(key =>{
let rows = {
name : (key as any).jobsUser.firstName,
jobType: 'Saved'
}
let job = {...rows,...(key as any).jobPosting};
this.result.push(job);
});
});
return this.result;
}
How to return the result to promise, I tried my best, But I don't know to do t, Maybe its because of two observable I have inside of it,
You would promisify both observables, and then use Promise.all to get a promise that fulfils when all is done:
parseJobs(userId: string) {
// Create a promise
const p1 = new Promise(resolve => {
this.getLikedJobs(userId).subscribe(result => {
// Resolve with the modified array
resolve(result.map(key =>{
let rows = {
name : (key as any).jobsUser.firstName,
jobType: 'Liked'
}
let job = {...rows,...(key as any).jobPosting};
// In a map, you want to return:
return job;
}));
});
});
// Same here:
const p2 = new Promise(resolve => {
this.getSavedJobs(userId).subscribe(result => {
resolve(result.map(key =>{
let rows = {
name : (key as any).jobsUser.firstName,
jobType: 'Saved'
}
let job = {...rows,...(key as any).jobPosting};
return job;
}));
});
});
// Return a promise that will fulfill when both promises fulfill
// and concatenate the results
return Promise.all([p1, p2]).then(result => [].concat(...result));
}
Now you don't store the result in this.result, but make it the promised value, which you get like this:
parseJobs(1).then(result =>
console.log(result);
});
You could of course still store the result in this.result, but that would not be best practice as it suggests that a piece of code may try to access it before it is available: you would always use the then method to get to the result.
Maybe you need something like that:
parseJobs(userId: string): Promise<any> {
let res: Function;
const resPromise = new Promise((resolve: Function) => {
// passing resolve function to upper scope
res = resolve;
});
this.getLikedJobs(userId).subscribe(result => {
...
// resolving result promise
res(result);
});
...
// unresolved
return resPromise;
}
You have 2 async calls. So there is also a single promise solution based on this knowledge.
parseJobs(userId: string) =>
new Promise(resolve => {
let result = [];
const done = (job) => {
result.push(job);
if(result.length === 2) {
resolve(result);
}
}
this.getLikedJobs(userId).subscribe(result => {
result.map(key =>{
let rows = {
name : (key as any).jobsUser.firstName,
jobType: 'Liked'
}
let job = {...rows,...(key as any).jobPosting};
done(job);
});
});
this.getSavedJobs(userId).subscribe(result => {
result.map(key =>{
let rows = {
name : (key as any).jobsUser.firstName,
jobType: 'Saved'
}
let job = {...rows,...(key as any).jobPosting};
done(job);
});
});
});
Also you may look at Promise.all method.

Categories

Resources