I have the following promise :
var aggregatePromise = () => {
return new Promise((resolve, reject) => {
EightWeekGamePlan.aggregate([
{
$match: {
LeadId: { $in: leads },
Week: week
}
},
{
$group: {
_id: {
LeadId: "$LeadId"
},
total: { $sum: "$TotalClaimsToBeClaimedByClientType" }
}
},
{
$match: {
total: { $lte: 5 - howManyClaims }
}
}
])
.then(leads => {
if (leads !== null) {
resolve(leads);
} else {
reject("Couldn't find any Leads");
}
})
.catch(err => {
reject(err);
});
});
};
I call it here :
// Step 2
var callAggregatePromise = async () => {
var result = await aggregatePromise();
return result;
};
And use it here :
//Step 3: make the call
callAggregatePromise().then(result => {
const winners = result.map(m => ({
LeadId: m._id.LeadId
}));
const flattened = winners
.reduce((c, v) => c.concat(v), [])
.map(o => o.LeadId);
console.log(flattened);
// Step 4 - declare 2ND Promise
var updateLeadsPromise = () => {
return new Promise((resolve, reject) => {
EightWeekGamePlan.updateMany(
{
LeadId: {
$in: flattened
},
TargetedToBeClaimedByClientType: groupTarget,
Week: week
},
{
$inc: {
TotalClaimsToBeClaimedByClientType: howManyClaims
}
}
)
.then(leadsUpdated => {
if (leadsUpdated !== null) {
resolve(leadsUpdated);
} else {
reject("Failed to update requested leads!");
}
})
.catch(err => {
reject(err);
});
});
};
//Step 5 : Call 2ND promise
var callUpdateLeadsPromise = async () => {
var resAgg = await updateLeadsPromise();
return resAgg;
};
//Step 6 : make the call to the "updateLeadsPromise"
callUpdateLeadsPromise().then(result1 => {
console.log(result1);
if (result1.ok === 1) {
// TODO
}
});
});
The problem is that Step 4)5)6) are dependent on the result of step 3).
How can I break the chain and make them independent ?
To break a chain you will have to await the Promises in order. It will still be a chain but in a different format. Btw throw away step 2 and 5.
To use await you have to have it inside a async function.
async function doLeadStuff() {
// awaiting main Promise
const result = await aggregatePromise();
const winners = result.map(m => ({
LeadId: m._id.LeadId
}));
const flattened = winners
.reduce((c, v) => c.concat(v), [])
.map(o => o.LeadId);
console.log(flattened);
// awaiting updates
const leadsUpdated = await EightWeekGamePlan.updateMany(
{
LeadId: {
$in: flattened
},
TargetedToBeClaimedByClientType: groupTarget,
Week: week
},
{
$inc: {
TotalClaimsToBeClaimedByClientType: howManyClaims
}
}
)
if (leadsUpdated === null) {
throw new Error("Failed to update requested leads!");
}
console.log(result1);
if (result1.ok === 1) {
// TODO
}
}
// calling async function
doLeadStuff()
Related
I have a list of jobs that should be sequentially executed.
As the jobs take seconds to be finished the should run in the background.
I thought that a job could be described as
interface Job {
name: string
execute(): Promise<boolean>
}
I would like to have a function which takes this list of jobs and execute them sequentially
until the list is completed or one job fails or is rejected, so basically:
function executeUntilFailed(jobs: Job[]): Promise<boolean>
{
// execute first job
// if this job
// - returns with true: continue with the next job
// - returns with false: resolve the promise with false
// - got rejected: reject the promise with the reason prefixed with the jobs name
//
// if there are no more jobs to do, resolve the promise with true
//
// basically it's a reduce operation with starting value of true and
// early stops if one job yields false or got rejected
}
I'm rather new to Javascript/Typescript and have a hard time implementing this.
Thanks,
Dieter
Thanks to Aluan Hadded and ehab.
I collected their solutions and have now the following code,
which does exactly what I need:
interface Job {
name: string
execute(): Promise<boolean>
}
async function executeUntilFailed(jobs: Job[]) {
for (const job of jobs) {
try {
if(!await job.execute()) {
return false
}
}
catch (err) {
throw new Error(`${job.name}: ${err.message}`)
}
}
return true
}
and here is some example for it
class JobImpl implements Job {
constructor(public name: string, private value: boolean, private throwMsg: string|null = null) {}
execute(): Promise<boolean> {
console.log(`executing job '${this.name}'`)
return new Promise((resolve,reject) => {
setTimeout(()=> {
if(this.throwMsg!=null) { reject(this.throwMsg) }
else { console.log(`finished job '${this.name}' with result: ${this.value}`); resolve(this.value) }
}, 1000)
})
}
}
const successJobs = [
new JobImpl("a", true),
new JobImpl("b", true),
new JobImpl("c", true),
]
const failedJobs = [
new JobImpl("d", true),
new JobImpl("e", false),
new JobImpl("f", true),
]
const throwingJobs = [
new JobImpl("g", true),
new JobImpl("g", true, "undefined problem"),
new JobImpl("i", true),
]
executeUntilFailed(successJobs)
.then((res) => console.log("resolved", res))
.catch((err) => console.log("rejected", err))
executeUntilFailed(failedJobs)
.then((res) => console.log("resolved", res))
.catch((err) => console.log("rejected", err))
executeUntilFailed(throwingJobs)
.then((res) => console.log("resolved", res))
.catch((err) => console.log("rejected", err))
<!-- end snippet -->
Just as an alternative, you could create a generator and then use the for await ... of syntax:
function * chainJobs(jobs) {
for (const job of jobs) {
yield job.execute().catch(err => new Error(`${job.name}: ${err.message}`));
}
}
async function executeUntilFailed(jobs) {
for await (const result of chainJobs(jobs)) {
if (!result) return false;
if (result instanceof Error) throw result;
}
return true;
}
You could achieve this either with a reduce function or a for of loop, i will show an implementation in a for of
async function executeUntilFailed(jobs) {
for (const job of jobs) {
try {
// notice that if a job resolved with false then it is considered a successful job
// This is normal and a promise resolved with false should not be considered an error
await job.execute()
// if u want based on your description to resolve the whole promise with false if one of promises resolved with false you could do
// const jobResult = await job.execute()
// if (jobResult === false) {
// return Prmise.resolve(false)
// }
} catch (err) {
return Promise.reject(new Error(`${job.name}_${err.toString()}`))
}
}
return Promise.resolve(true)
}
lets see the function in action
const successJobs = [{
name: "a",
execute: () => Promise.resolve(1)
},
{
name: "b",
execute: () => Promise.resolve(2),
},
{
name: "c",
execute: () => Promise.resolve(3)
},
]
const failedJobs = [{
name: "a",
execute: () => Promise.resolve(1)
},
{
name: "b",
execute: () => Promise.reject(new Error("undefined problem")),
},
{
name: "c",
execute: () => Promise.resolve(3)
},
]
async function executeUntilFailed(jobs) {
for (const job of jobs) {
try {
await job.execute()
} catch (err) {
return Promise.reject(new Error(`${job.name}_${err.toString()}`))
}
}
return Promise.resolve(true)
}
console.log(
executeUntilFailed(successJobs)
.then((res) => console.log("resolved", res))
.catch((err) => console.log("rejected", err))
)
console.log(
executeUntilFailed(failedJobs)
.then((res) => console.log("resolved", res))
.catch((err) => console.log("rejected", err))
)
First, I know this is a common question. I'm trying to get a handle on how to use async / await in place of setTimeouts, but all the examples I see online use a setTimeout to simulate the async. This throws me off when it's a set timeout that I'm trying to replace.
In the function below, I want this.filteredResultsto await the results of an API call before trying to filter those results and assign it to this.filteredResults.
getResults() {
let allResults= airtableQuery.getTable("Transfers"); // API call using imported 'getTable' function
console.log(allResults); // returns full array ▶[] although it's not available for filtering yet.
setTimeout(() => { // I want to replace this timeout
this.filteredResults = allResults.filter(
(result) => result.fields.User === "dev"
);
}, 250); // random ms that is roughly how long airtableQuery takes for the API call.
},
And the airtableQuery:
getTable(table) {
let recordsArr = [];
base(`${table}`)
.select({
maxRecords: 8000,
})
.eachPage(
function page(records, fetchNextPage) {
records.forEach((record) => {
recordsArr.push(record);
});
fetchNextPage();
},
function done(err) {
if (err) {
this.$toasted.error(err);
}
}
);
return recordsArr;
},
Please make the outer function an async function and then await the results before filtering them.
async function getResults() {
let allResults = await airtableQuery.getTable("Transfers");
this.filteredResults = allResults.filter(
(result) => result.fields.User === "dev"
);
},
Given that getTable() is not a Promise, await will not do anything. For that reason, we can make getTable() return a Promise which will resolve with recordsArr.
getTable(table) {
return new Promise((resolve, reject) => {
let recordsArr = [];
base(`${table}`)
.select({
maxRecords: 8000,
})
.eachPage(
function page(records, fetchNextPage) {
records.forEach((record) => {
recordsArr.push(record);
});
fetchNextPage();
},
function done(err) {
if (err) {
this.$toasted.error(err);
reject(err)
}else {
resolve(recordsArr)
}
}
);
})
}
Hope it helps.
i always likes primise,this my code show you
getTable(table) {
return new Promise((res, rej) => {
let recordsArr = [];
base(`${table}`)
.select({
maxRecords: 8000,
})
.eachPage(
function page(records, fetchNextPage) {
records.forEach((record) => {
recordsArr.push(record);
});
fetchNextPage();
res(recordsArr)
},
function done(err) {
if (err) {
this.$toasted.error(err);
rej(err)
}
}
);
})
}
getResults() {
airtableQuery.getTable("Transfers").then(res => {
let allResults = res
console.log(allResults);
this.filteredResults = allResults.filter(
(result) => result.fields.User === "dev"
);
});
}
I am using a library that calls an API, and I am waiting for Promise return to receive an Array.
However, even though I expect 2 elements in ActivityItem array, sometimes I receive only first of them (the one that appears first (Item1). From my point of view, I implemented the Promise incorrectly and there should be a mistake in the way I return them, but I miss seeing it.
Here I call the function that uses that should return Promise:
componentDidMount() {
this.getDataFromKit(ONEDAYINTERVAL).then(result => {
this.sendDataToServer(result); //sending to backend
}).catch(e => console.error);
}
And here is a method itself:
getDataFromKit(dateFrom) {
return new Promise((resolve) => {
AppleKit.initKit(KitPermissions.uploadBasicKitData(), (err, results) => {
if (err) {
return;
}
AppleKit.getSamples(dateFrom, (err, results) => {
if (err) {
return resolve([]);
}
const newData = results.map(item => {
return { ...item, name: "Item1" };
});
const allData = [...this.state.ActivityItem, ...newData];
this.setState({ ActivityItem: allData });
resolve(allData);
});
// if I delete the code below it will work just fine always grabbing only one item.
new Promise((resolve) => {
AppleKit.getSamplesSecondMethod(dateFrom, (err, results) => {
if (err) {
return resolve([]);
}
const newData = results.map(item => {
return { ...item, name: "Item2" };
});
const allData = [...this.state.ActivityItem, ...newData];
this.setState({ ActivityItem: allData });
resolve(allData);
});
});
});
})
}
The main issue here is I guess: how can I return multiple promises from this one method?
The problem as I see it, is your second block of code doesn't get run because you are resolving the promise in the first block. The way you have it coded, you will want to resolve that promise only once all your async operations have completed. I modified your code, but have not tested it. It may need to add the .then method to make sure the async data is returned before resolving the initial promise.
What happens if you try this ?
UPDATE
It looks like the below code solved your problem as you accepted my answer. However, I did re-write it before I realized you accepted so I will add the new updated code in case that will help you or someone else.
Original Answer
getDataFromKit(dateFrom) {
const thenable = new Promise((resolve) => {
AppleKit.initKit(KitPermissions.uploadBasicKitData(), (err, results) => {
if (err) {
return;
}
AppleKit.getSamples(dateFrom, (err, results) => {
if (err) {
return resolve([]);
}
const newData = results.map(item => {
return {
...item,
name: "Item1"
};
});
resolve(newData);
});
});
})
.then((newData) => {
AppleKit.initKit(KitPermissions.uploadBasicKitData(), (err, results) => {
if (err) {
return;
}
AppleKit.getSamplesSecondMethod(dateFrom, (err, results) => {
if (err) {
return;
}
var stateData = this.state.ActivityItem;
const addData = results.map(item => {
return {
...item,
name: "Item2"
};
});
stateData = [...stateData, ...newData];
stateData = [...stateData, ...addData];
this.setState({
ActivityItem: stateData
});
});
});
});
return thenable;
}
Updated Code using Promise.all
getDataFromKit(dateFrom) {
return new Promise((resolve) => {
const promise1 = new Promise((resolve) => {
AppleKit.initKit(KitPermissions.uploadBasicKitData(), (err, results) => {
if (err) {
return Promise.reject(err);
}
AppleKit.getSamples(dateFrom, (err, results) => {
if (err) {
return Promise.reject(err);
}
const newData = results.map(item => {
return {
...item,
name: "Item1"
};
});
return Promise.resolve(newData);
});
});
});
const promise2 = new Promise((resolve) => {
AppleKit.initKit(KitPermissions.uploadBasicKitData(), (err, results) => {
if (err) {
return Promise.reject(err);
}
AppleKit.getSamplesSecondMethod(dateFrom, (err, results) => {
if (err) {
return Promise.reject(err);
}
const moreData = results.map(item => {
return {
...item,
name: "Item2"
};
});
return Promise.resolve(moreData);
});
});
});
Promise.all([promise1, promise2])
.then(([result1, result2]) => {
var nArrays = [result1, result2, this.state.ActivityItem];
const finalResult = [].concat(...nArrays);
return Promise.resolve(finalResult);
});
});
}
how can I add async & await to this sequence of filters to make sure that the results of one filter is returned before the next filter is called?
let rooms = [
{
type: "bedroom",
name: "Caroline",
utility: {
size: "90-150",
shape: "square",
}
let hasType = async (type, object) => object.type === type;
let resultType = rooms.filter(x => hasType("bedroom", x));
console.log(resultType);
let hasSize = (size, object) => object.utility.size === size;
let resultTypePlusSize = resultType.filter(x => hasSize("90-150", x));
console.log(resultTypePlusSize);
let hasShape = (shape, object) => object.utility.shape === shape;
let resultTypePlusSizePlusShape = resultTypePlusSize.filter(
x => hasShape("square", x));
console.log(resultTypePlusSizePlusShape);
You could use reduce to build up promise chains and then Promise.all to execute multiple:
function chain(...mappers) {
return {
exec(start) {
return mappers.reduce((value, fn) => {
if(value instanceof Promise) {
return value.then(fn);
} else {
return fn(value);
}
}, start);
},
many(values) {
return Promise.all(values.map(val => this.exec(val)));
}
};
}
To be used as:
const transformUsers = chain(
user => (user.name = "test", user),
async user => user.name,
console.log
);
transformUsers.many([{ }, { }]);
I'm having yet another async issue where I'm lost and have no idea where or how to fix it. Forgive my bad naming.
api call to twitch api and returns an array its results.
exports.batchPromiseWrapper = function(arr) {
const filteredMungedDataArr = [];
let promiseBatachArray = arr.map(vod_id => {
var url = `https://api.twitch.tv/kraken/videos/${vod_id.id}/markers`;
var params = { api_version: 5 };
return axios
.get(url, {
params: params,
headers: {
"Client-ID": "xxxxxxxxxxxxxxx"
}
})
.then(res => {
return res.data;
})
.catch(function(error) {
console.log(error);
});
});
return Promise.all(promiseBatachArray)
.then(markers => {
if (markers !== null) {
markers.map(markerObj => {
if (markerObj.markers.game_changes !== null) {
markerObj.markers.game_changes.forEach(gameName => {
if (gameName.label === "Fortnite") {
filteredMungedDataArr.push(markerObj);
}
});
}
});
return filteredMungedDataArr;
}
})
.catch(err => {
if (err.status === 500 || err.status === 404) {
console.log("error: ", err, err.message);
}
});
};
The data looks like this:
[[1,2,3,4,5],[1,2,3,4,5]], generator will yield and make a promise.all call of 5 before pausing 5sec and continuing to the next batch of 5.
exports.batchFetchingGeneratorWrapper = function(generator, batchArray) {
let evalNextValue = generator.next();
let delay = (v, t) => {
return new Promise(resolve => {
setTimeout(resolve.bind(null, v), t);
});
};
if (!evalNextValue.done) {
exports.batchPromiseWrapper(evalNextValue.value).then(data => {
let newBatchArray = batchArray;
if (data !== undefined) {
newBatchArray = batchArray.concat(data);
}
delay(5000).then(() => {
exports.batchFetchingGeneratorWrapper(generator, newBatchArray);
});
});
} else {
console.log("yay done!", batchArray);
return batchArray;
}
};
I'm able to console the results in batchArray from batchFetchingGeneratorWrapper, but I unable to act on it and I know it has something to do with async and how it has yet to be resolved.
promiseDataWrapper
.then(data => {
return gatherData.cleanUpVODData(data);
})
.then(data => {
function* batchFetching(batchArray) {
for (let i = 0; i < batchArray.length; i++) {
yield batchArray[i];
}
}
let batchArrResult = [];
let g = batchFetching(data);
new Promise((resolve, reject) => {
gatherData.batchFetchingGeneratorWrapper(g, batchArrResult);
if (g.done) { // i dont think this works
console.log("batchArrResult 1: ", batchArrResult);
resolve(batchArrResult);
}
}).then(result => console.log("asdfasdf", batchArrResult)); // empty array is returned
});
As far as I can tell, the problem lies chiefly in batchFetchingGeneratorWrapper().
It should be a matter of :
fixing delay()
making appropriate returns to make the recursion work
ensuring that the function returns Promise.
Almost undoubtedly (syntactically) simpler with async/await but here it is with old-fashioned thens :
exports.batchFetchingGeneratorWrapper = function(generator, batchArray) {
let evalNextValue = generator.next();
let delay = (t) => {
return new Promise(resolve => {
setTimeout(resolve, t);
});
};
if (!evalNextValue.done) {
return exports.batchPromiseWrapper(evalNextValue.value).then(data => {
return delay(5000).then(() => {
return exports.batchFetchingGeneratorWrapper(generator, batchArray.concat(data || []));
});
});
} else {
console.log("yay done!", batchArray);
return Promise.resolve(batchArray); // <<< promise wrapped to ensure that batchFetchingGeneratorWrapper() returns Promise
}
};
And chain the batchFetchingGeneratorWrapper() call appropriately :
promiseDataWrapper
.then(data => gatherData.cleanUpVODData(data))
.then(data => {
function* batchFetching(batchArray) {
for (let i = 0; i < batchArray.length; i++) {
yield batchArray[i];
}
}
return gatherData.batchFetchingGeneratorWrapper(batchFetching(data), []).then(batchArrResult => {
console.log('batchArrResult: ', batchArrResult);
return batchArrResult;
});
}).catch(error => {
console.log(error);
});