I want to test each element of an array until a condition is met then skip the rest. This is the code I came up with, it seems to be working but I'm not sure if its actually safe or it has unexpected side effects. Other solutions are welcomed.
let buddyAdded = false;
replicaArr = _.keys(ReplicaList);
Promise.each(replicaArr, function(replicaname) {
if (!buddyAdded) {
Player.count({
'buddyList': replicaname
}, function(err, result) {
if (err) {
} else if (result < 990) {
Player.update({
'_id': buddyObj.buddyId
}, {
'buddyList': replicaname
}, function(err) {
if (err) {
} else {
ReplicaList[replicaname].send(buddyObj);
buddyAdded = true;
// success stop here and skip all the other array elements
return;
}
});
}
});
}
});
If you're trying to enumerate the players serially one at a time and abort the iteration when you find a player with room in their buddy list that you can update the list and communicate back any errors that happen, then here's one way of doing it.
Here's how this works:
Use Bluebird's Promise.promisifyAll() to automatically make promise returning methods for the Player object so we can then use those in our control flow.
Use Bluebird's Promise.mapSeries() to iterate the array serially one at a time.
Chain the Player.countAsync() and Player.updateAsync() methods so they sequence properly and return them from .mapSeries() so it waits for them to complete before continuing the iteration to the next array element.
If we find a player with room and successfully update its buddy list, then throw a specially exception. This will reject the current promise chain and cause .mapSeries() to stop it's iteration (which is what you said you wanted).
Add a .catch() at the higher level that tests for the special rejection and turns it into a successful resolved promise. If it's some other error, then let that continue to propagate as an actual error.
The code:
// Promisify the Player object so the methods
// this would usually be done wherever this module is loaded
Player = Promise.promisifyAll(Player);
// create a special subclass of Error for our short circuit
PlayerUpdateDone extends Error {
constructor(name) {
super();
this.name = name;
}
}
// put logic into a function to make it cleaner to use
function addToBuddyList(replicaArr) {
return Promise.mapSeries(replicaArr, function(replicaname) {
return Player.countAsync({buddyList: replicaname}).then(function(result) {
// if room left in buddy list
if (result < 990) {
return Player.updateAsync({'_id': buddyObj.buddyId}, {'buddyList': replicaname}).then(function() {
ReplicaList[replicaname].send(buddyObj);
// throw special exception to abort .mapSeries()
// and stop further processing
throw new PlayerUpdateDone(replicaname);
});
}
});
}).then(function() {
// if it gets here, there were no players with rooms so just return null
return null;
}).catch(function(result) {
// we used a special rejection as a shortcut to stop the mapSeries from executing
// the rest of the series
if (result instanceof PlayerUpdateDone) {
// change this special rejection into a result
return result.name;
}
// must have been regular error so let that propagate
throw result;
});
}
// sample usage
addToBuddyList(replicaArr).then(function(name) {
if (name) {
console.log(`Updated player ${name}`);
} else {
console.log("No players with room in their buddy list");
}
}).catch(function(err) {
// error here
console.log(err);
});
It may be simpler to make your own sequencer that stops when the first promise resolves to a truthy value:
// fn will be passed each array element in sequence
// fn should return a promise that when resolved with a truthy value will stop the iteration
// and that value will be the resolved value of the promise that is returned from this function
// Any rejection will stop the iteration with a rejection
Promise.firstToPassInSequence = function(arr, fn) {
let index = 0;
function next() {
if (index < arr.length) {
return Promise.resolve().then(function() {
// if fn() throws, this will turn into a rejection
// if fn does not return a promise, it is wrapped into a promise
return Promise.resolve(fn(arr[index++])).then(function(val) {
return val ? val : next();
});
});
}
// make sure we always return a promise, even if array is empty
return Promise.resolve(null);
}
return next();
};
Promise.firstToPassInSequence(replicaArr, function(replicaname) {
return Player.countAsync({buddyList: replicaname}).then(function(result) {
// if room left in buddy list
if (result < 990) {
return Player.updateAsync({'_id': buddyObj.buddyId}, {'buddyList': replicaname}).then(function() {
ReplicaList[replicaname].send(buddyObj);
return replicaname;
});
}
});
});
Related
I'm trying to write a promise polyfill to get a better understanding of promise.
I've searched the internet and found a code which I'm able to understand to some extent.
function CustomPromise(executor) {
var state=PENDING;
var value = null;
var handlers=[];
var catchers = [];
function resolve(result) {
if(state!==PENDING) return;
state=FULFILLED;
value = result;
handlers.forEach((h)=>h(value)); //this line
}
function reject(error) {
if(state!==PENDING)return;
state=REJECTED;
value=error;
catchers.forEach(c=>c(value)); // this line
}
this.then = function(successCallback) {
if(state === FULFILLED) {
successCallback(value);
}else {
handlers.push(successCallback);
}
return this;
}
this.catch = function(failureCallback) {
if(state===REJECTED){
failureCallback(value)
} else {
catchers.push(value);
}
}
executor(resolve,reject);
}
Even in this I'm unable to understand the use of handlers and catchers. It was said that they are for situation when promise is not fulfilled or rejected. Explaining these two lines will also help.
Now, the actual issue with above implementation is it doesn't work for when used like let p1 = Promise.resolve("Hello World");. I have tried converting it to class based but I'm unable to do that.
My attempt:
class CustomPromise {
constructor(callback){
this.state = PENDING;
this.executor = callback;
this.value = null;
this.handlers = [];
this.catchers = [];
this.then = function(successCallback) {
if(this.state === FULFILLED) {
successCallback(this.value);
}else {
this.handlers.push(successCallback);
}
return this;
};
this.catch = function(failureCallback) {
if(this.state===REJECTED){
failureCallback(this.value)
} else {
this.catchers.push(this.value);
}
};
}
static resolve(result) {
if(this.state!==PENDING) return;
this.state=FULFILLED;
this.value = result;
this.handlers.forEach((h)=>h(this.value));
// return new CustomPromise( function ( fulfil ) {
// fulfil( value );
// });
}
static reject(error) {
if(this.state!==PENDING)return;
this.state=REJECTED;
this.value=error;
this.catchers.forEach(c=>c(this.value));
}
// executor(resolve,reject);
}
Can someone correct the functional approach so that it works for CustomPromise.resolve() scenario or correction in my class based approach will also be appreciated.
EDIT: Tried CustomPromise.prototype.resolve = function(error) {...}
still getting same error CustomPromise.resolve is not a function
EDIT2 : In class based approach I'm unable to implement executor callback. I just want either one of the approach to work for case like Promise.resolve()
To extend the first (working) version of CustomPromise, add the code you seem to have been trying with in commented-out code in the (non-working) class-version you had. But it had two little problems. Still, the idea to use new CustomPromise is the right one:
CustomPromise.resolve = value => new CustomPromise(fulfil => fulfil(value));
CustomPromise.reject = error => new CustomPromise((_, reject) => reject(error));
So if you add that below your CustomPromise function definition, it'll work:
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";
function CustomPromise(executor) {
var state=PENDING;
var value = null;
var handlers=[];
var catchers = [];
function resolve(result) {
if(state!==PENDING) return;
state=FULFILLED;
value = result;
handlers.forEach((h)=>h(value));
}
function reject(error) {
if(state!==PENDING)return;
state=REJECTED;
value=error;
catchers.forEach(c=>c(value));
}
this.then = function(successCallback) {
if(state === FULFILLED) {
successCallback(value);
}else {
handlers.push(successCallback);
}
return this;
}
this.catch = function(failureCallback) {
if(state===REJECTED){
failureCallback(value)
} else {
catchers.push(value);
}
}
executor(resolve,reject);
}
// Added:
CustomPromise.resolve = value => new CustomPromise(fulfil => fulfil(value));
CustomPromise.reject = error => new CustomPromise((_, reject) => reject(error));
// Demo
let p = CustomPromise.resolve(42);
let q = CustomPromise.reject("custom error");
p.then(value => console.log("p", value));
q.catch(error => console.log("q", error));
Disclaimer: this polyfill is not compliant with the Promises/A+ specification let be it would be compliant with the ECMAScript specification for Promise.
Some of the problems it has:
It allows the then callback to be executed synchronously. The then callback should never be executed synchronously, but always be called via a queued job (i.e. asynchronously).
The catch method wrongly returns undefined. It should return a promise.
The then method wrongly returns the same promise as it is called on. It should return a different promise, that resolves with the value that will be returned by the callback.
The then method ignores a second argument. It should take a second function which should serve as a rejection callback (like catch).
When the promise is resolved with a thenable, the thenable is wrongly used as fulfillment value. This is not how it is supposed to work. A very important aspect of promises, is that promises chain, i.e. when a promise resolves to another promise (or thenable), it gets "locked in" to that second promise, so that it will follow the way that second promise resolves.
There are many correct implementations to be found. I posted my own Promises/A+ compliant implementation in this answer. It does not have the resolve and reject static methods, but it would require the same additional code as given above.
Suppose that newsService.getNews() returns a promise that should resolve to a random news entry returned by some service, while translateService.translate() returns a promise that should resolve to the translation of the passed text.
var newsPromises = [];
var translatePromises = [];
for (var i = 0; i < 5; i++) {
var p1 = this.newsService.getNews();
newsPromises.push(p1);
p1.then(function (data) {
var p2 = this.translateService.translate(data);
translatePromises.push(p2);
p2.then(function (translatedData) {
addNews(`${data} (${translatedData})`);
}, function (fail) {
console.log(fail.message);
});
}, function (fail) {
console.log(fail.message);
});
}
now the page initially shows a loading spinner that I would like to hide when all the promises (including the nested translation promises) have completed (succeeded or failed):
Promise.all(newsPromises)
.then(function (results) {
Promise.all(translatePromises).then(function (results) {
removeLoading();
},
function (err) {
removeLoading();
}
);
}, function (err) {
Promise.all(translatePromises).then(function (results) {
removeLoading();
},
function (err) {
removeLoading();
}
);
});
This code a) does not work as it should, since the loading spinner some times disappears before the promises resolve, and b) is horribly complex.
How is this done properly? (with vanilla JS / ES6)
Remember that promises chains are pipelines, where each handler can transform the chain's result as the result passes through the handler. See comments:
// We only need one array of promises
const promises = [];
// Build the array
for (let i = 0; i < 5; i++) {
// Add this promise to the array
promises.push(
// Get the news...
this.newsService.getNews().then(
// ...and translate it...
data => this.translateService.translate(data)
.then(translatedData => {
// ...and show it as soon as it's available
addNews(`${data} (${translatedData})`);
// Note that here we're converting the resolution value to
// `undefined`, but nothing uses it so...
// If you want something to be able to use it,
// return `translatedData` (or `data` or...)
})
)
.catch(fail => {
console.log(fail.message);
// WARNING: Here you're converting rejection to resolution with `undefined`
})
);
}
// Wait until all that is done before removing the loading indicator
Promise.all(promises).then(removeLoading);
Note that the only reason we don't need a catch on the Promise.all promise is that you're ignoring (other than logging) errors that occur, so we know that promise will never reject.
Also note that the above assumes removeLoading doesn't pay any attention to the arguments it receives, and that it doesn't return a promise that may reject. If it does care about arguments and it's important to call it with no arguments, change the Promise.all bit to:
Promise.all(promises).then(() => removeLoading());
If it returns a promise that may reject, you'll need a catch handler as well.
in such cases i creating global counter loadersCount = 0
each time you call this.newsService.getNews() call function loaderStart()
and each time you call addNews() or console.log(fail.message) call loaderStop()
function loaderStart () {
if (loadersCount === 0) {
addLoading();
}
loadersCount++;
}
function loaderStop () {
if (loadersCount === 1) {
removeLoading();
}
loadersCount--;
}
I've read that you should avoid nested promises in JavaScript, as they tend to be an antipattern, but I'm having trouble figuring out how to avoid them in my particular use case. Hopefully someone with more experience than me can see where I'm going wrong? Any advice would be much appreciated!!
Essentially, I'm retrieving some data asynchronously, processing it and catching a possible Error, and then saving some data asynchronously. Here's a much-simplified example:
class Foo {
changeName(path, newName) {
this.getPage(path) // AJAX call, returns a promise with type Page,
// may throw an Error if the page does not exist.
.then(page=> {
// Some modifications are made to page, omitted here
return page
})
.catch(e=>{
if(e instanceof PageDoesNotExistError) {
return new Page();
}
})
.then(page=> {
page.name = newName;
this.savePage(path, page); // ******
// I want my outer changeName method to return this ^ promise,
// or at least a promise that will resolve when this promise
// resolves, with the same value.
})
}
}
How can I have changeName return a promise that will resolve with the value of this.savePage (the line marked with //******), so that I could do something like this elsewhere:
myFooInstance.changeName('/path', 'New Name').then(page=> {
// Do something with the returned saved page
});
How can I have changeName return a promise that will resolve with the value of this.savePage
Return the promise savePage returns from the then handler. That will resolve the promise created by then to the promise from savePage, meaning that the promise created by then will be fulfilled or rejected depending on what the promise from savePage does. (Details on that terminology in my blog post here if you're interested.)
.then(page=> {
page.name = newName;
return this.savePage(path, page);
})
Separately, you've said you want the caller to be able to use the promise on the return value of changePage but you're not returning anything out of changePage. You need to add return in front of the whole structure so you return the ultimate promise.
changeName(path, newName) {
return this.getPage(path) // AJAX call, returns a promise with type Page,
// ...
(See below for a complete version.)
Side note: You have an error waiting to happen here:
.catch(e=>{
if(e instanceof PageDoesNotExistError) {
return new Page();
}
})
If e is not an instance of PageDoesNotExistError, you're converting the rejection into a resolution with the value undefined, because your catch handler doesn't return an explicit value in that case. If you want to propagate the error instead, you need to do that with throw e or return Promise.reject(e):
.catch(e=>{
if(e instanceof PageDoesNotExistError) {
return new Page();
}
return Promise.reject(e);
})
Bringing all three of those things together:
class Foo {
changeName(path, newName) {
return this.getPage(path)
.then(page=> {
// Some modifications are made to page, omitted here
return page;
})
.catch(e=>{
if(e instanceof PageDoesNotExistError) {
return new Page();
}
return Promise.reject(e);
})
.then(page=> {
page.name = newName;
return this.savePage(path, page);
});
}
}
You would just return the promise and in turn return from any nested promise:
class Foo {
changeName(path, newName) {
return this.getPage(path)
.catch(e => {
if(e instanceof PageDoesNotExistError) {
return new Page();
}
})
.then(page=> {
page.name = newName;
return this.savePage(path, page); // assuming that this is a promise
})
}
}
Suppose I have the the following Promise chain:
var result = Promise.resolve(filename)
.then(unpackDataFromFile)
.then(transformData)
.then(compileDara)
.then(writeData);
Now I have not only one transformData function but two or more, stored in an array. I want to try the first one, and if the compileData function fails, try the second one and so on until either compileData succeeds or the array of transformData functions is exhausted.
Can someone give me an example on how to implement this?
Running all transformData functions and give the result array to compileData is not an option, since the functions are very expensive and I want to run as few as possible of them.
transformData itself also returns a Promise, if that helps.
I would start by isolating the notion of trying a number of promises until one succeeds:
function tryMultiple([promise, ...rest]) {
if (!promise) throw new Error("no more to try");
return promise.catch(() => tryMultiple(rest));
}
Now write a handler which tries each combination of transforming and compiling:
function transformAndCompile(transformers) {
return function(data) {
return tryMultiple(transformers.map(t => t(data).then(compileData)));
};
}
Now the top level is just:
var result = Promise.resolve(filename)
.then(unpackDataFromFile)
.then(transformAndCompile(transformers))
.then(writeData);
By the way, Promise.resolve(filename).then(unpackDataFromFile) is just a roundabout way of saying unpackDataFromFile(filename).
You can do something like this:
// various transformer functions to try in order to be tried
var transformers = [f1, f2, f3, f4];
function transformFile(filename) {
// initialize tIndex to select next transformer function
var tIndex = 0;
var p = unpackDataFromFile(filename);
function run() {
return p.then(transformers[tIndex++])
.then(compileData)
.catch(function(err) {
if (tIndex < transformers.length) {
// execute the next transformer, returning
// a promise so it is linked into the chain
return run();
} else {
// out of transformers, so reject and stop
throw new Error("No transformer succeeded");
}
}).then(writeData);
}
return run();
}
transformFile("someData.txt").then(function(finalResult) {
// succeeded here
}).catch(function(err) {
// error here
});
Here's how this works:
Sets up a tIndex variable that indexes into the array of transformer functions.
Calls unpackDataFromFile(filename) and saves the resulting promise.
Then executes the sequence p.then(transformer).then(compileData) using the first transformer. If that succeeds, it calls writeData and returns the resulting promise.
If either the transformer or compileData fails, then it goes to the next transformer function and starts over. The key here to making this work is that in the .catch() handler, it returns a new promise which chains into the originally returned promise. Each new call to run() is chained onto the original promise from unpackDataFromFile() which allows you to reuse that result.
Here's a bit more generic implementation that makes an iterator for an array that iterates until the iterator callback returns a promise that fulfills.
// Iterate an array using an iterator that returns a promise
// Stop iterating as soon as you get a fulfilled promise from the iterator
// Pass:
// p - Initial promise (can be just Promise.resolve(data))
// array - array of items to pass to the iterator one at a time
// fn - iterator function that returns a promise
// iterator called as fn(data, item)
// data - fulfilled value of promise passed in
// item - array item for this iteration
function iterateAsyncUntilSuccess(p, array, fn) {
var index = 0;
function next() {
if (index < array.length) {
var item = array[index++];
return p.then(function(data) {
return fn(data, item).catch(function(err) {
// if this one fails, try the next one
return next();
});
});
} else {
return Promise.reject(new Error("End of data with no operation successful"));
}
}
return next();
}
// Usage:
// various transformer functions to try in order to be tried
var transformers = [f1, f2, f3, f4];
iterateAsyncUntil(unpackDataFromFile(filename), transformers, function(data, item) {
return item(data).then(compileData);
}).then(writeData).then(function(result) {
// successfully completed here
}).catch(function(err) {
// error here
});
The following should do what you want most idiomatically:
var transformers = [transformData, transformData2];
var result = unpackDataFromFile(filename)
.then(function transpile(data, i = 0) {
return transformers[i](data).then(compileData)
.catch(e => ++i < transformers.length? transpile(data, i) : Promise.reject(e));
})
.then(writeData);
Basically you recurse on the transformers array, using .catch().
I've been learning promises using bluebird for two weeks now. I have them mostly understood, but I went to go solve a few related problems and it seems my knowledge has fell apart. I'm trying to do this simple code:
var someGlobal = true;
whilePromsie(function() {
return someGlobal;
}, function(result) { // possibly even use return value of 1st parm?
// keep running this promise code
return new Promise(....).then(....);
});
as a concrete example:
// This is some very contrived functionality, but let's pretend this is
// doing something external: ajax call, db call, filesystem call, etc.
// Simply return a number between 0-999 after a 0-999 millisecond
// fake delay.
function getNextItem() {
return new Promise.delay(Math.random()*1000).then(function() {
Promise.cast(Math.floor(Math.random() * 1000));
});
}
promiseWhile(function() {
// this will never return false in my example so run forever
return getNextItem() !== false;
}, // how to have result == return value of getNextItem()?
function(result) {
result.then(function(x) {
// do some work ...
}).catch(function(err) {
console.warn("A nasty error occured!: ", err);
});
}).then(function(result) {
console.log("The while finally ended!");
});
Now I've done my homework! There is the same question, but geared toward Q.js here:
Correct way to write loops for promise.
But the accepted answers, as well as additional answers:
Are geared toward Q.js or RSVP
The only answer geared toward bluebird uses recursion. These seems like it's likely to cause a huge stack overflow in an infinite loop such as mine? Or at best, be very inefficient and create a very large stack for nothing? If I'm wrong, then fine! Let me know.
Don't allow you to use result of the condition. Although this isn't requirement -- I'm just curious if it's possible. The code I'm writing, one use case needs it, the other doesn't.
Now, there is an answer regarding RSVP that uses this async() method. And what really confuses me is bluebird documents and I even see code for a Promise.async() call in the repository, but I don't see it in my latest copy of bluebird. Is it in the git repository only or something?
It's not 100% clear what you're trying to do, but I'll write an answer that does the following things you mention:
Loops until some condition in your code is met
Allows you to use a delay between loop iterations
Allows you to get and process the final result
Works with Bluebird (I'll code to the ES6 promise standard which will work with Bluebird or native promises)
Does not have stack build-up
First, let's assume you have some async function that returns a promise whose result is used to determine whether to continue looping or not.
function getNextItem() {
return new Promise.delay(Math.random()*1000).then(function() {
return(Math.floor(Math.random() * 1000));
});
}
Now, you want to loop until the value returned meets some condition
function processLoop(delay) {
return new Promise(function(resolve, reject) {
var results = [];
function next() {
getNextItem().then(function(val) {
// add to result array
results.push(val);
if (val < 100) {
// found a val < 100, so be done with the loop
resolve(results);
} else {
// run another iteration of the loop after delay
setTimeout(next, delay);
}
}, reject);
}
// start first iteration of the loop
next();
});
}
processLoop(100).then(function(results) {
// process results here
}, function(err) {
// error here
});
If you wanted to make this more generic so you could pass in the function and comparison, you could do this:
function processLoop(mainFn, compareFn, delay) {
return new Promise(function(resolve, reject) {
var results = [];
function next() {
mainFn().then(function(val) {
// add to result array
results.push(val);
if (compareFn(val))
// found a val < 100, so be done with the loop
resolve(results);
} else {
// run another iteration of the loop after delay
if (delay) {
setTimeout(next, delay);
} else {
next();
}
}
}, reject);
}
// start first iteration of the loop
next();
});
}
processLoop(getNextItem, function(val) {
return val < 100;
}, 100).then(function(results) {
// process results here
}, function(err) {
// error here
});
Your attempts at a structure like this:
return getNextItem() !== false;
Can't work because getNextItem() returns a promise which is always !== false since a promise is an object so that can't work. If you want to test a promise, you have to use .then() to get its value and you have to do the comparson asynchronously so you can't directly return a value like that.
Note: While these implementations use a function that calls itself, this does not cause stack build-up because they call themselves asynchronously. That means the stack has already completely unwound before the function calls itself again, thus there is no stack build-up. This will always be the case from a .then() handler since the Promise specification requires that a .then() handler is not called until the stack has returned to "platform code" which means it has unwound all regular "user code" before calling the .then() handler.
Using async and await in ES7
In ES7, you can use async and await to "pause" a loop. That can make this type of iteration a lot simpler to code. This looks structurally more like a typical synchronous loop. It uses await to wait on promises and because the function is declared async, it always returns a promise:
function delay(t) {
return new Promise(resolve => {
setTimeout(resolve, t);
});
}
async function processLoop(mainFn, compareFn, timeDelay) {
var results = [];
// loop until condition is met
while (true) {
let val = await mainFn();
results.push(val);
if (compareFn(val)) {
return results;
} else {
if (timeDelay) {
await delay(timeDelay);
}
}
}
}
processLoop(getNextItem, function(val) {
return val < 100;
}, 100).then(function(results) {
// process results here
}, function(err) {
// error here
});