Well I understand that one would have to return a promise, and not the result of a promise to pass promises around.
However I seem to be unable to implement this, say I have a member method like:
CreateNextBatch() {
this.orders.clear();
let maxNum = this.maxNum;
let counter = this.orderCounter;
let CreateThem = (counter, r) => {
if (r >= 0) {
//Order.find() finds an entry in a database
Order
.find({orderNr: counter.fullNumber()})
.then(function(orders) {
console.log("created order " + counter.fullNumber().toString());
let num = r;
if (orders.length === 0) {
this.OpenOrder(counter.fullNumber());
//adds order to this.orders
num -= 1;
}
counter.nextNumber();
return CreateThem(counter, num);
}.bind(this))
.catch (function (err){
console.log(err);
return false;
});
} else {
return true;
}
};
return () => {CreateThem(counter, maxNum);};
}
Basically it creates orders in a recursive fashion, terminating after finding this.MaxRequests empty spots and then puts them together in a list under this.orders
Now I called this function by:
initialLoader.CreateNextBatch().then(function (success) {
console.log(success);
console.log("loaded");
initialLoader.initializeBatch();
});
However this fails:
TypeError: initialLoader.CreateNextBatch(...).then is not a function
at LoadExternDatabase...
Why isn't this working? What am I not understanding yet?
Edit: I've also tried to replace the return by a new promise:
return new Promise((resolve, reject) => {
CreateThem(counter, maxRequests);
resolve();
});
However this executes the resolve immediatelly, instead of waiting for CreateThem to complete. Nor directly the function by return CreateThem.bind(this, counter, maxRequests);
You should resolve a promise after the asynchronous operation done.
For example:
function test() {
return new Promise((resolve, reject) => {
setTimeout(() => {
// Resolved here!
// Because asynchronous function `setTimeout` ends here
resolve();
}, 1000);
})
}
In your code, if r >= 0, it will find an entry in database again until r less than 0, so it ends at else block:
if (r >= 0) {
// ...
} else {
// ends here
}
Maybe you could change your code to:
CreateNextBatch() {
return new Promise((resolve, reject) => {
this.orders.clear();
let maxNum = this.maxNum;
let counter = this.orderCounter;
let CreateThem = (counter, r) => {
if (r >= 0) {
//Order.find() finds an entry in a database
Order
.find({orderNr: counter.fullNumber()})
.then(function(orders) {
console.log("created order " + counter.fullNumber().toString());
let num = r;
if (orders.length === 0) {
this.OpenOrder(counter.fullNumber());
//adds order to this.orders
num -= 1;
}
counter.nextNumber();
return CreateThem(counter, num);
}.bind(this))
.catch (function (err){
console.log(err);
reject(err);
});
} else {
resolve(true);
}
};
});
// return () => {CreateThem(counter, maxNum);};
}
return new Promise((resolve, reject) => {
globalVarKeep = resolve;
globalVarBreak = reject;
});
Before that you would have had some code waiting for a promise or an event that says: -
CreateThem(counter, maxRequests);
globalVarKeep();
or
CreateThem(counter, maxRequests).then(globalVarKeep);
If that's appropriate.
The function initialLoader.CreateNextBatch() returns a function, not a promise, you should do: initialLoader.CreateNextBatch()()
The function CreateThem can return true if this.maxNum >= 0 and a promise of true or false depending on the recursion. But doesn't allways guaranteed return a promise. And () => {CreateThem(counter, maxNum);}; doesn't return anything either. You could try the following:
CreateNextBatch() {
this.orders.clear();
let maxNum = this.maxNum;
let counter = this.orderCounter;
let CreateThem = (counter, r) => {
if (r >= 0) {
//Order.find() finds an entry in a database
//you can return a promise here
return Order
.find({orderNr: counter.fullNumber()})
.then(function(orders) {
console.log("created order " + counter.fullNumber().toString());
let num = r;
if (orders.length === 0) {
this.OpenOrder(counter.fullNumber());
//adds order to this.orders
num -= 1;
}
counter.nextNumber();
return CreateThem(counter, num);
}.bind(this))
.catch (function (err){
console.log(err);
return false;
});
} else {
return Promise.resolve(true);
}
};
//you are not returning CreateThem here
return () => {return CreateThem(counter, maxNum);};
}
Related
I'm writing a method to iterate through a series of items to find either the next incomplete or the last one that returns a promise.
The item status is fetched by another method that also returns a promise and I'm trying to resolve/reject within the resolve callback of this method.
Simplified version of my attempt below:
export const findNextEntry = (i) => {
let length = entries.length
let entry = entries[i]
entry.isLastEntry = i + 1 >= length
return new Promise((resolve, reject) => {
getEntryStatus(i).then(() => {
if (entry.status.complete) {
if ((i + 1) < length) {
findNextEntry(i + 1)
} else {
reject('no more entries')
}
} else {
resolve('entry set')
}
})
})
And then I call the method:
findNextEntry(entry.number).then((resolve) => {
console.log(resolve)
}, (reject) => {
console.log(reject)
})
The resolve/reject callbacks don't seem to be firing, and I get an error Uncaught (in promise) no more entries.
Probebly you must call reject/resolve for nested iterations
export const findNextEntry = (i) => {
let length = entries.length
let entry = entries[i]
entry.isLastEntry = i + 1 >= length
return new Promise((resolve, reject) => {
getEntryStatus(i).then(() => {
if (entry.status.complete) {
if ((i + 1) < length) {
findNextEntry(i + 1).then(resolve).catch(reject)
} else {
reject('no more entries')
}
} else {
resolve('entry set')
}
})
})
Similar but more simple code
function test(i = 0) {
console.log(i);
return new Promise((resolve, reject) => {
if (i < 10) test(i + 1).then(resolve).catch(reject);
else resolve('done');
});
}
test();
I suspect I've fundementally misunderstood Javascript promises, any ideas?
I have a pretty function that queries a database containing music that looks like this:
function searchDatabaseForTrack(query,loadedResults){
loadedResults = loadedResults || [];
desiredResults = 100;
if (loadedResults.length < desiredResults) {
try {
databaseApi.searchTracks(query, {"offset":loadedResults.length, "limit":"50", }).then(function(data){
i=0
if (data.tracks.items.length == 0) {
console.log(`Already loaded all ${loadedResults.length} tracks!`)
console.log(loadedResults)
return loadedResults;
}
else {
for (thing in data.tracks.items){
loadedResults.push(data.tracks.items[i]);
i=i+1;
}
console.log(loadedResults.length, " tracks collected");
searchDatabaseForTrack(query,loadedResults)
}
});
} catch(err) {
console.log("ERROR!", err)
console.log(loadedResults)
return loadedResults;
}
} else {
console.log(loadedResults)
return loadedResults;
}
}
And then a bit later, I try to call and use the data retrieved.
function getArtistTracks(artistName){
searchDatabaseForTrack(artistName).then(function(data){
console.log(songs);
songs.sort(function(a,b){
var c = new Date(a.track.album.release_date);
var d = new Date(b.track.album.release_date);
return d-c;
});
console.log("songs", songs);
var newsongs=[];
i=0
for (song in songs) {
newsongs.push(songs[i].track.uri);
i++
};
return newsongs;
});
}
What I'm trying to do is get the second function "getArtistTracks" to wait for the completion of the query in the first function. Now I could just call the databaseApi.searchTracks directly, but there's a limit of 50 tracks returned per result — which kind of screws me over.
searchDatabaseForTrack().then(...) shouldn't work since searchDatabaseForTrack() doesn't return a promise, so you can either return a promise or use an async function.
instead of a recursive function, you could simply call databaseApi in a for loop,
the desiredResult should be an argument and not hardcoded in the function,
async function searchDatabaseForTrack(query, desiredResults){
let loadedResults = [], data, currOffset = 0;
const iterations = Math.ceil(desiredResults / 50);
for(let n = 0 ; n < iterations; n++){
cuurOffset = n * 50;
data = await databaseApi.searchTracks(query, {"offset":currOffset, "limit":"50", });
if (data.tracks.items.length == 0) {
console.log(`Already loaded all ${loadedResults.length} tracks!`)
console.log(loadedResults)
return loadedResults;
}
else {
loadedResults = loadedResults.concat(data.tracks.items);
console.log(loadedResults.length, " tracks collected");
}
}
return loadedResults;
}
the rest should be fine as long as you add .catch() to handle errors ( as mentionned in previous answer ) which are thrown automatically without the need of the try/catch block :
function getArtistTracks(artistName, 100){
searchDatabaseForTrack(artistName).then((songs) => {
// your previous code
})
.catch((err) => {
// handle error
});
});
Have searchDatabaseForTrack use Promise.all to return the loadedResults after all results have been gotten. Also, make sure not to implicitly create global variables as you're doing with thing. For example, try something like this:
async function searchDatabaseForTrack(query) {
const desiredResults = 100;
const trackPromises = Array.from(
({ length: Math.ceil(desiredResults / 50) }),
(_, i) => {
const offset = i * 50;
return databaseApi.searchTracks(query, { offset, limit: 50 });
}
);
const itemChunks = await Promise.all(trackPromises);
const loadedResults = itemChunks.reduce((a, { tracks: { items }}) => (
[...a, ...items]
), []);
return loadedResults;
};
and
searchDatabaseForTrack(artistName).then((loadedResults) => {
// do stuff with loadedResults
})
.catch((err) => {
console.log("ERROR!", err)
// handle error
});
Suppose I have two asynchronous functions, A and B, which are independent to each other.
What I am trying to do is that execute these functions sequentially multiple time as shown below
A -> B -> A -> B -> A -> ...
B waits until A finishes and vice versa.
The following is what I have done so far and I know it is not going to work the way I want.
function A() {
var promise = new Promise...
...
return promise;
}
function B() {
var promise = new Promise...
...
return promise;
}
for(var i=0; i<200; i++) {
var p1 = A();
p1.then(() => {
var p2 = B();
// ----
}
}
How should I change the code?
You're headed the right way, but you need to keep chaining the thens. You generally start with a pre-resolved promise from Promise.resolve() then add to the chain using then, keeping each new promise:
let p = Promise.resolve();
for (var i=0; i<200; i++) {
p = p.then(A).then(B);
}
p.then(() => {
console.log("All done");
});
Live Example (with 20 instead of 200):
let counterA = 0;
let counterB = 0;
function A() {
var promise = new Promise(resolve => {
setTimeout(() => {
++counterA;
console.log("A done (" + counterA + ")");
resolve();
}, 100);
});
return promise;
}
function B() {
var promise = new Promise(resolve => {
setTimeout(() => {
++counterB;
console.log("B done (" + counterB + ")");
resolve();
}, 100);
});
return promise;
}
let p = Promise.resolve();
for (var i=0; i<20; i++) {
p = p.then(A).then(B);
}
p.then(() => {
console.log("All done");
});
.as-console-wrapper {
max-height: 100% !important;
}
(In real code, you'd have a .catch as well to handle rejections, of course; or you'd be returning the last promise to other code that would handle them.)
You can chain calls with chained .then() to make sure they are called after the previous ones are done.
let cA = cB = 0;
function A() {
return new Promise(resolve => {
setTimeout(() => {
resolve("A " + ++cA);
console.log("A done");
}, 200);
});
}
function B() {
return new Promise(resolve => {
setTimeout(() => {
resolve("B " + ++cB);
console.log("B done");
}, 300);
});
}
function callConsecutive(times) {
if (times == 0) return Promise.resolve([]);
times--;
const a = A(),
b = a.then(B),
c = b.then(() => { return callConsecutive(times) });
return Promise.all([a, b, c]).then(([r1,r2,r3]) => Promise.resolve([r1,r2,...r3]));
}
callConsecutive(5).then(results => { console.log(results); })
You can do it with a recursive function
function seqAB(count) {
if(!count) Promise.resolve();
var p1 = A();
p1.then(() => {
var p2 = B();
p2.then(() => {
seqAB(count - 1);
})
})
}
seqAB(200);
I am new to using Promise and breaking my head to solve this. I want to run a script until a condition is met in protractor with below code but it's not working. how to fix this?
let counter = 30;
for (let i = 0; i < counter; i++) {
browser.executeScript(somescript).then((value) => {
//console.log(value);
if (value > 0)
//do some stuff
}
else {
return; //exit for loop --not working
//or
i=counter;// to exit if i and counter value same
}
})
}
I have this limitedRetry code that would appear to be what you need
limitedRetry = (cont, fn) => fn().catch(err => cont > 0 ? limitedRetry(cont - 1, fn) : Promise.reject(err));
Your use would be
limitedRetry(30, function() {
browser.executeScript(somescript).then((value) => {
if (value > 0) {
// do some stuff
return 'this will be the resolved value of the promise';
} else {
return Promise.reject('try again');
}
});
})
.then(v => console.log(v))
.catch(err => console.log(err));
Now you're firing all executeScripts at the same time, and in the worst case the last script (result) may return first. You should start the next executeScript only if you have the result of the previous executeScript.
This can be done by 'listening' to the result of the previous promise, and calling executeScript after you know what the previous result was. I've replaced somescript with the variable i, so that the result is more determinate. In case you're just looking for a retry, see the other answer.
var browser = {
executeScript: function(i) {
return Promise.resolve(i);
}
}
var initial = Promise.resolve(null);
for (var i = 0; i < 30; i++) {
(function(locali) {
initial = initial.then(function(prev) {
if(prev === undefined) return;
return browser.executeScript(locali).then(function (value) {
console.log(value);
if (value < 5) {
return value;
} else {
return;
}
});
});
})(i);
}
in a Node.js App, i want to achieve this:
read an array, depend of item type, decide to use an specific function that returns a Q Promise object. i want this process runs sequentially.
i have this two Promises:
var q = require('q');
Classes.prototype.fn1 = function (item) {
return q.Promise(function(resolve, reject){
Items.find({item_published: true}).exec(
function (err, events) {
if (err) {
reject('an error happened');
throw err;
}else{
resolve(events);
}
});
});
};
Classes.prototype.fn2 = function (item) {
return q.Promise(function(resolve, reject){
resolve(item);
});
};
And this is main code:
self.items = [{},{},{}]; //some items
self.result = [];
self.items.forEach(function(item,index,array){
if(item.type == 1){
self.fn1(item)
.then(function(result){
self.result.push(result);
})
}
if(item.type == 2){
self.fn2(item)
.then(function(result){
self.result.push(result);
})
}
if(index == array.length-1){
callback(self.result);
}
});
But it does not work. because that fn1 has a Async process, it runs after fn2. All i want is running these functions sequentially even one of them has Async process.
You can use .reduce to chain the promises.
var promise = q(); // Create a Resolved promise for chaining.
self.items = [{},{},{}]; //some items
self.result = [];
// We put an resolved promise as init value for chaining
self.items.reduce(function(chain, item) {
// Don't do anything if item type is not match
if (item.type !== 1 && item.type !== 2) {
return chain;
}
var targetFunc = null;
if (item.type === 1) {
targetFunc = self.fn1;
} else if (item.type === 2) {
targetFunc = self.fn2;
}
if (targetFunc === null) {
return chain;
}
// Chain the promise and return the last of the chain.
return chain
.then(function(){
return targetFunc(item);
})
.then(function(result){
// This then will get the result from above
// so we put the result to self.result here
self.result.push(result);
});
}, promise).then(function() {
// When all promises are sequentially resolved,
// call the callback with self.resul.
callback(self.result);
});
jsfiddle, jsfiddle-Q.js ver.
Had something very similar to below cooking ... but fuyushimoya's just too fast, though we handle reduce's initialization differently
var promises = self.items.map( function(item) {
if (item.type == 2) {
return self.fn1(item);
}
else if (item.type == 3) {
return self.fn2(item);
}
});
function handler(p) {
return p.then( function(res) {
self.result.push(res);
});
}
promises
.reduce( function(prev, current) {
if (prev) {
return prev.then( function() { handler(current) } )
}
else {
return handler(current)
}
})
.then(function(result) {
callback(null, result);
})
.catch( // error handler);
Sketching it out. But something like this might work.
UPD: the trick was to chain promises as it was mentioned in comments, there is updated version that I used for code snippet here:
var items = [{type:1},{type:2},{type:1}];
var result = [];
var rq = Q();
var ql = rq;
items.forEach(function (it, ix) {
ql = ql.then(function(){
var dp = "fn" + it.type;
return ps[dp]();
})
.then(function(d) {
result.push(d);
});
});
ql.then(function() {
callback(result);
});