The requirement is finishing the current function before moving to the next call:
var current_data = something;
Run(current_data).then((data1) => {
Run(data1).then(data2 => {
Run(data2).then(data3 => {
// and so on
})
})
});
The example above is only possible if I know exactly how much data I want to get.
In order to make the nested promises part of promise chain, you need to return the nested promises.
Run(current_data).then((data1) => {
return Run(data1).then(data2 => {
return Run(data2).then .....
});
});
I'm gonna assume your data is paginated and you don't know how many pages there are, therefore you can use a while loop with await inside of an async function like so:
(async function() {
var currentData = someInitialData;
// loop will break after you've processed all the data
while (currentData.hasMoreData()) {
// get next bunch of data & set it as current
currentData = await Run(currentData);
// do some processing here or whatever
}
})();
You can use the async-await to make code more readable.
async function getData(current_data){
let data1 = await Run(current_data)
let data2 = await Run(data1);
let result = await Run(data2);
return result;
}
Calling the getData function
getData(data)
.then(response => console.log(response))
.catch(error => console.log(error));
Try to avoid nested promises. If you need to call a series of promises, which depend on the previous call's response, then you should instead chain then like the following following -
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('foo');
}, 1000);
});
promise1.then((response) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(response + ' b');
}, 1000);
});
}).then((responseB) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(responseB + ' c');
}, 1000);
});
}).then((responseC) => {
console.log(responseC); // 'foo b c'
})
if your code can support async-await then what Mohammed Ashfaq suggested is an alternative.
If you are executing the same function over and over again but on different data, I would make a recursive function that returns return a Promise.
I just look at my example below using an an array of numbers, you can edit it to your current case.
var current_data = [1,2,4,5,6]
function Run(data){
if(data.length === 0)
return Promise.resolve(data);
return new Promise((resolve, reject)=>{
//your async operation
//wait one second before resolving
setTimeout(()=>{
data.pop()
console.log(data)
resolve(data)
},1000)
})
.then((results)=>{
return Run(results)
})
}
Run(current_data)
Related
I am using a library call to connect to my vendor. The libary call requires a callback in the call. Without a callback in the function, I can easily make this synchronous. With the Callback, everything I do is stuck in the callback and never bubbles it way out.
I have literally tried 100 different ways to get this to work.
function removeFromDNC(emailAddress, accessToken_in)
{
return new Promise( function(resolve, reject)
{
try{
const options =
{
auth: {
accessToken: accessToken_in
}
, soapEndpoint: 'https://webservice.XXX.exacttarget.com/Service.asmx'
};
var co = {
"CustomerKey": "DNC",
"Keys":[
{"Key":{"Name":"Email Address","Value": emailAddress}}]
};
var uo = {
SaveOptions: [{"SaveOption":{PropertyName:"DataExtensionObject",SaveAction:"Delete"}}]
};
const soapClient = new FuelSoap(options);
//again, I don't control the structure of the next call.
let res = soapClient.delete('DataExtensionObject', co, uo, async function( err, response ) {
if ( err ) {
// I can get here, but my reject, or if I use return, does nothing
reject();
}else{
// I can get here, but my reject, or if I use return, does nothing
resolve();
}
});
console.log("res value " + res); // undefined - of course
}catch(err){
console.log("ALERT: Bad response back for removeFromDNC for email: " + emailAddress + " error: " + err);
console.log("removeFromDNC promise fulfilled in catch");
reject();
}
});
}
Both methods resolve and reject expect parameters, which are res and err in your case.
As far as removeFromDNC returns a Promise instance, you should call it using either async/await syntax:
const res = await removeFromDNC(...);
or chaining then/catch calls:
removeFromDNC(...)
.then((res) => { ... }) // resolve
.catch((err) => { ... }) // reject
EDIT:
If you want to avoid usage of callbacks inside removeFromDNC, consider promisifying of soapClient.delete call. Refer to util.promisify() if you working in Node.js or use own implementation.
Here is the example for demonstration:
const promisify = (fun) => (...args) => {
return new Promise((resolve, reject) => {
fun(...args, (err, result) => {
if(err) reject(err);
else resolve(result);
})
})
}
const soapClient = {
delete: (value, cb) => {
setTimeout(() => cb(null, value), 10);
}
};
async function removeFromDNC(emailAddress, accessToken_in) {
const soapDelete = promisify(soapClient.delete.bind(soapClient));
const res = await soapDelete('Soap Responce');
//You can use res here
return res;
}
removeFromDNC().then(res => console.log(res))
I have the following code that is used to get JSON data from an Amazon Web Server API.
var json1 = new Promise((resolve, reject) => {
fetch(url[0])
.then(r => {
resolve(r.json())
})
.catch(err => {
reject(err)
})
})
I have this repeating 14 times using different urls and json vars and have it return the promises at the end using.
return Promise.all([json1,json2,json3,json4,json5,json6,json7,json8,json9,json10,json11,json12,json13,json14]).then(function(values) {
return values;
});
This works, but it takes up 150+ lines. I want to make a for loop that runs through the same code using a for loop. I created this...
for(var jsonCount = 0;jsonCount<url.length-1;jsonCount++){
jsonArr[jsonCount] = new Promise((resolve, reject) => {
fetch(url[jsonCount])
.then(r => {
resolve(r.json())
})
.catch(err => {
reject(err)
})
})
}
This doesn't work because the promise functions come back as undefined even though it is called by an await function.
const data = await fetchURL(urlToQuery())
Does anyone have suggestions to make this work? There is JSON being returned.
Thanks for your help.
Edit: Here is a larger chunk of the code.
function fetchURL(urls) {
let fetchJson = url => fetch(url).then(response => response.json());
Promise.all(urls.map(fetchJson)).then(arr => {
return arr;
});
(async function() {
const data = await fetchURL(urlToQuery())
console.log(data);
for(var r=0;r<numStations;r++){
if (data[r] == ""){
onlineArr[r] = false;
wdDataArr[r].push(cardinalToDeg(stationHistAvgArr[r]));
wsDataArr[r].push(0);
You can use .map for the loop. But don't use new Promise. You don't need a new promise when fetch already provides you with one.
Also, call your array urls instead of url. A plural will be a good indication for the reader of your code that indeed it is a collection of URLs.
Here is how it could look:
let fetchJson = url => fetch(url).then(response => response.json());
Promise.all(urls.map(fetchJson)).then(arr => {
// process your data
for (let obj of arr) {
console.log(obj);
}
});
I think this example can helps you:
// Mock async function
const getDataAsync = callback => {
setTimeout(
() => callback(Math.ceil(Math.random() * 100)),
Math.random() * 1000 + 2000
)
}
// Create the promise
const getDataWithPromise = () => {
return new Promise((resolve, reject) => {
try {
getDataAsync(resolve);
} catch(e) {
reject(e);
}
});
}
// Using the promise one time
getDataWithPromise()
.then(data => console.log("Simple promise:",data))
.catch(error => console.error(`Error catched ${error}`));
// Promises compound: Promise.all
const promise1 = getDataWithPromise();
promise1.then(data => console.log("promise1 ends:",data));
const promise2 = getDataWithPromise();
promise2.then(data => console.log("promise2 ends:",data));
const promise3 = getDataWithPromise();
promise3.then(data => console.log("promise3 ends:",data));
const promise4 = getDataWithPromise();
promise4.then(data => console.log("promise4 ends:",data));
const promise5 = getDataWithPromise();
promise5.then(data => console.log("promise5 ends:",data));
Promise.all([promise1,promise2,promise3,promise4,promise5])
.then(data => console.log("Promise all ends !!",data));
Hope this helps
you will have issues with closure and var variable capture.
You may want to change var to let to capture the right value in the closure so that url[jsonCount] is actually what you want.
also I think it would be much easier to do something like that in one line :)
let results = [];
for(let i = 0; i < urls.length; ++i) results.push(await (await fetch[urls[i]]).json());
This is a good use for map, mapping urls to promises...
function fetchUrls(urls) {
let promises = urls.map(url => fetch(url))
return Promise.all(promises).then(results => {
return results.map(result => result.json())
})
}}
// url is your array of urls (which would be better named as a plural)
fetchUrls(url).then(results => {
// results will be the fetched json
})
Using the async/await syntax (equivalent meaning)
// this can be called with await from within another async function
async function fetchUrls(urls) {
let promises = urls.map(url => fetch(url))
let results = await Promise.all(promises)
return results.map(result => result.json())
}
I have a script which involves data processing and then uploading/downloading files in mass.
I've been trying to find different ways to sort out an issue I'm having and I've decided I want to try something like this (it appears to be the easiest solution), but I'm not sure if it's achievable with JavaScript.
I'm not that great with async and I have no clue what to do.
Thanks in advance, I've left a vague idea of what I'm doing below.
function one() {
>> do something | data processing
};
function two() {
>> do something | file upload/download
};
function thr() {
>> do something | process two arrays for an output that will be pushed to a global var
};
one();//wait for one to finish
two();//wait for two to finish
thr();//wait for thr to finish
function two() {
return new Promise( async (resolve, reject) => {
fs.createReadStream('data.csv')
.pipe(csv())
.on('data', (row) => {
if (row[removed] === '') return;
const
url1 = row[removed],
url2 = row[removed]),
https = require('https'),
id = row[removed]);
let upload = [];
const fetchRealUrl = request(url1, (e, response) => {//need to await completion
const FILENAMEREMOVED1= fs.createWriteStream(`${id}-FILENAMEREMOVED1`),
downloadRealFile = https.get(response.request.uri.href, function(response) {
response.pipe(FILENAMEREMOVED1);
console.log(`FILENAMEREMOVED file ${id} downloaded.`);
});
}),
fetchRealUrl2 = request(url2, (e, response) => {//need to await completion
const FILENAMEREMOVED2= fs.createWriteStream(`${id}-FILENAMEREMOVED2`),//need to extract into funciton instead of repeating
downloadRealFile2= https.get(response.request.uri.href, function(response) {
response.pipe(FILENAMEREMOVED2);
console.log(`FILENAMEREMOVEDfile ${id} downloaded.`);
});
});
//getData(url);
upload.push(`{"id":"${id}",REMOVED}`);
})
.on('end', (row) => {
console.info('Completed .');
resolve(upload);//completes while downloads still running
});
});
}
Try looking at Promises.
function one() {
return new Promise((resolve, reject) => {
//Do something...
let x = 10
resolve(x)
}
}
function two() {
return new Promise((resolve, reject) => {
//Do something else...
let y = 20
resolve(y)
}
}
one().then(x => { //The value resolved in one() is passed here
//then() is executed only when one() resolves its promise
return two()
}).then(y => { //The value resolved in two() is passed here
//etc...
})
const func1 = new Promise(res =>{
setTimeout(() => {
//do some asynchronous work
res()
}, 1000)
})
const func2 = new Promise(res =>{
setTimeout(() => {
//do some asynchronous work
res()
}, 1000)
})
//To run async functions in a waterfall pattern:
func1()
.then(resultOfFunc1 => {
//do something with resultOfFunc1
return func2()
})
.then(resultOfFunc2 => {
//do something with resultOfFunc2
})
//To run async functions in a parallel pattern:
let promiseFunctions = [func1(), func2()]
let result = Promise.all(promiseFunctions)
//result will be an array of resolved promises
I have a situation where I think the only choice for me is to nest some Promises within each other. I have a Promise that needs to be performed and a method that does something until that Promise is complete. Something like this:
let promise = new Promise((resolve, reject) => {
// Do some stuff
});
doSomethingUntilPromiseisDone(promise);
However, within my Promise, I need to execute another method that returns another Promise:
let promise = new Promise((resolve, reject) => {
fetchValue(url)
.then((value) => {
// Do something here
}).catch((err) => {
console.error(err);
});
});
doSomethingUntilPromiseisDone(promise);
But now, in the fetchValue method's then statement, I have another method I need to execute that, guess what, returns another Promise:
let promise = new Promise((resolve, reject) => {
fetchValue(url)
.then((value) => {
saveToCache(value)
.then((success) => {
console.log('success!!');
resolve('success');
});
}).catch((err) => {
console.error(err);
});
});
doSomethingUntilPromiseisDone(promise);
So in the end, I have a Promise, within a Promise, within a Promise. Is there someway I can structure this better so that it is more straightforward? It seems like nesting them within each other is counter to Promise's intended chaining approach.
Use .then()
let doStuff = (resolve, reject) => {/* resolve() or reject() */};
let promise = new Promise(doStuff);
doSomethingUntilPromiseisDone(
promise
.then(value => fetchValue(url))
.then(value => value.blob())
.then(saveToCache)
)
.then(success => console.log("success!!"))
.catch(err => console.error(err))
you can use generator to flatten your nested promises (Bluebird.couroutine or Generators)
//Bluebird.couroutine
const generator = Promise.coroutine(function*() {
try {
const value = yield fetchValue(url);
const success = yield saveToCache(value);
console.log('success:', success);
} catch(e) {
console.error(err);
}
}));
generator();
Each function will call the next one with the result of the method before.
var promises = [1,2,3].map((guid)=>{
return (param)=> {
console.log("param", param);
var id = guid;
return new Promise(resolve => {
// resolve in a random amount of time
setTimeout(function () {
resolve(id);
}, (Math.random() * 1.5 | 0) * 1000);
});
}
}).reduce(function (acc, curr, index) {
return acc.then(function (res) {
return curr(res[index-1]).then(function (result) {
console.log("result", result);
res.push(result);
return res;
});
});
}, Promise.resolve([]));
promises.then(console.log);
Without the use of any extra libraries (async, bluebird etc) I am trying to implement a function that returns a promise resolves (or rejects) based on an array of functions that return promises as an input parameter... Promise.all(iterable) has very similar functionality to what I am trying to accomplish exepct the promises in the iterable parameter of Promise.all do not execute in sequential order.
I could simply chain these functions together however what if functionListwas a list of unkown length...
I have attemped to conceptually show what I am trying to accomplish below:
function foo() {
return new Promise((resolve, reject) => {
setTimeout( () => {
return resolve();
}, 200);
})
}
function bar() {
return new Promise((resolve, reject) => {
setTimeout( () => {
return resolve();
}, 200);
})
}
function baz() {
return new Promise((resolve, reject) => {
setTimeout( () => {
return reject();
}, 100);
})
}
const functionList = [foo, bar, baz];
function promiseSeries(functionList){
const results = [];
return new Promise((resolve, reject) => {
promiseList.map((promise, i) => {
return new Promise((_resolve, _reject) => {
promise()
.then((result) => {
results.push(result);
if (i === promiseList.length - 1) {
return resolve(results);
}
else return _resolve();
})
.catch(error => error)
})
})
})
}
Obviously with running Promise.all on functionList we will see that baz (even though its position is functionList[2] resolves first)
Neither Resolve promises one after another (i.e. in sequence)? or Running promises in small concurrent batches (no more than X at a time) provide answers to this question. I do not want to import a library to handle this single utility function and mostly I am just curious as to what this function would look like.
This is not more complicated than:
funcs.reduce((prev, cur) => prev.then(cur), Promise.resolve())
A recursive version, which return an array of the resolved values:
function recurse([first, ...last], init) {
if (!first) return Promise.resolve([]);
return first(init)
.then(value =>
recurse(last, value)
.then(values => [value, ...values])
);
}
// Testing function to return a function which
// returns a promise which increments its value.
const x = v => y => Promise.resolve(v + y);
recurse([x(1), x(2)], 0).then(console.log);
A nice and clean way to achieve this without any extra libraries is using recursion. An example:
const p1 = () => Promise.resolve(10);
const p2 = () => Promise.resolve(20);
const p3 = () => Promise.resolve(30);
const promiseList = [p1, p2, p3];
const sequencePromises = (promises) => {
const sequence = (promises, results) => {
if (promises.length > 0) {
return promises[0]()
.then(res => sequence(promises.splice(1, promises.length), results.concat(res)))
.catch(err => Promise.reject(err));
} else {
return Promise.resolve(results);
}
}
return sequence(promises, []);
}
sequencePromises(promiseList)
.then(res => console.log(res))
.catch(err => console.log("ERROR"));
The simplest way is to just chain them, starting with a null promise:
const promises = [foo, bar, baz];
let result = Promise.resolve();
promises.forEach(promise => result = result.then(() => promise));
return result;
As Ali pointed out, you can use a reduction instead but you need to use functions in the then calls:
return promises.reduce(
(result, promise) => result.then(() => promise),
Promise.resolve()
);
You can omit the initial value if you know that promises is not empty.
However if you really want to do things in sequence, you often want to deal with an array of functions that return a promise. For example:
return ids.map(id => () => processId(id))
.reduce((p, fn) => p.then(fn), Promise.resolve());
promises.reduce((previous, promise) => {
if (previous) {
return previous.then(() => promise);
}
return promise;
}, null)