I am using Firebase Functions with Unity. The main function returns before the database functions finish. I am still new to Node.js and I am still trying to get my head around all the Async Callback stuff.
I have tried CallAsync, ContinueWith, and Coroutines, but the function always continues after the first return (I use Task.isCompleted() to check for that).
My Node.js functions are something like this:
exports.my_fn = functions.https.onCall((data, context) => {
dbsessions.child(id).once("value").then(function(snapshot) {
if (snapshot.val()) {
Create();
} else {
Move(session);
}});
});
function Move(session) {
if (session["error"]) {
return return_stuff;
} else {
if (some_condition) {
dbsessions.child(id).set(sson, function(set_error) {
if (set_error) {
return return_stuff;
} else {
return return_stuff;
}
});
} else {
dbaimoves.child(stt).child(dif).once("value").then(function(snapshot) {
if (snapshot.val()) {
return return_stuff;
} else {
if (!first) {
dbsessions.child(id).set(sson, function(set_error) {
if (set_error) {
return return_stuff;
} else {
return return_stuff;
}
});
} else {
return return_stuff;
}
}
}, function(errorObject) {
if (errorObject) {
return return_stuff;
}
});
}}}
var Create = function(data, callback) {
dbdifficulty.child(data).once("value").then(function(snapshot) {
if (snapshot.val()) {
return callback();
} else {
dbsessions.child(data.id).set(data, function(set_error) {
if (set_error) {
return callback();
} else {
return callback();
}});
}});
}
(I skipped unnecessary data to keep the question simple). It is basically nested returns and database operations, callbacks and functions call each other.
My C# Unity code is something like this:
private async Task<string> AddMessageAsync(string text)
{
// Create the arguments of the callable function.
var data = new Dictionary<string, string>();
data["s"] = text;
data["d"] = "0";
var function = func.GetHttpsCallable("my_fn");
var Tfn = function.CallAsync(data);
var TRes = await Tfn;
if (Tfn.IsCompleted)
{
JFunc result = JsonUtility.FromJson<JFunc>(TRes.Data.ToString());
Debug.Log("error:" + result.error);
return result.move;
}
return "error";
}
The codes above resemble my actual code, which calls the function from Unity, the function runs on Firebase and returns shortly (before it goes into Create() or Move()), Unity receives the result (null). A few seconds later the function finishes successfully on Firebase, but Unity does not receive anything about that (or maybe it does, but I can't handle it properly).
I need to know:
how to make the main function return what the other functions return, and
how to make C# wait and keep listening to the returned values, instead of thinking the task has completed after the first return. It would be even better if I can only return only when the result is ready.
To make the Cloud Functions code return a value, make sure that each function returns a value or a promise. Promises "bubble up" meaning that the value you return from the most-nested code will be return to the top-level, as long as you have a return on each level.
So in your code from a quick scan, you need:
exports.my_fn = functions.https.onCall((data, context) => {
return dbsessions.child(id).once("value").then(function(snapshot) {
if (snapshot.val()) {
return Create();
} else {
return Move(session);
}});
});
var Create = function(data, callback) {
return dbdifficulty.child(data).once("value").then(function(snapshot) {
if (snapshot.val()) {
return callback();
} else {
return dbsessions.child(data.id).set(data, function(set_error) {
if (set_error) {
return callback();
} else {
return callback();
}});
}});
}
I've only instrumented the top-level my_fn and Create here, to show what to do. You'll have to do the same for Move yourself.
Here is my Code:
Checking if user is folowing official Twitter Account (here I've returned new Promise
var isFollowing = function(sender) {
return new promise(function(resolve, reject) {
var following = false;
var params = {
source_id: sender,
target_screen_name: config.get('twitter.official_account')
};
TwitObj.get('friendships/show', params, function(err, data) {
if (!err) {
following = data.relationship.source.following;
resolve(following);
} else {
reject(err);
}
});
});
};
Validation:
var validateMsg = function(dm) {
var sender = getSender(dm);
var result = [];
isFollowing(sender.id)
.then(function(response) {
if (!response) {
result = interactiveMessage(false, lang.__('FOLLOW_MAIN', sender.name));
console.log(result); // Displays as expected
return result; // Not returning value Do I need to return promise again? If yes, WHY?
}
});
};
Main Implementation:
var direct_message = function(msg) {
verifyAccount().catch(function(err) {
console.error(err);
});
var dm = msg.direct_message;
var result = validateMsg(dm);
console.log(result);
};
Questions is how should I force validateMsg function to return result variable inside then function.
Update: While debugging, I got to know that, console.log(response) in
validation function is displaying later after throwing undefined in
"then" function which means program is not able to get response
immediately and due to async nature, I/O is not getting blocked. How
to tackle this?
You're not actually returning anything in the validateMsg function. You're returning a synchronous value ( result ) in the .then function which is a separate function.
The second thing to consider is that you're expecting the validateMsg function, which is asynchronous to behave synchronously.
var result = validateMsg(dm);
The way to achieve what I believe you're looking to do involves making the following changes:
1) Return the promise chain in the validateMsg function.
var validateMsg = function(dm) {
var sender = getSender(dm);
var result = [];
return isFollowing(sender.id)
.then(function(response) {
if (!response) {
result = interactiveMessage(false, lang.__('FOLLOW_MAIN', sender.name));
return result;
}
// Not sure what you want to do here if there is a response!
// At the moment you're not doing anything if the validation
// is successful!
return response;
});
};
2) Change your function call, since you're now returning a promise.
validateMsg(dm).then( function( result ){
console.log( result );
})
3) Consider adding a catch somewhere if only to help you debug :)
validateMsg(dm).then( function( result ){
console.log( result );
})
.catch( function( err ){
console.log( "Err:::", err );
});
The validationMsg function does not have a return value, thus the result in the main function has the value undefined. Try to put return in front of isFollowing so the function returns a promise, then treat validationMsg as a promise.
I'm running into an issue which I don't fully understand. I feel like there are likely concepts which I haven't grasped, code that could be optimized, and possibly a bug thrown in for good measure.
To greatly simplify the overall flow:
A request is made to an external API
The returned JSON object is parsed and scanned for link references
If any link references are found, additional requests are made to populate/replace link references with real JSON data
Once all link references have been replaced, the original request is returned and used to build content
Here, is the original request (#1):
await Store.get(Constants.Contentful.ENTRY, Contentful[page.file])
Store.get is represented by:
async get(type, id) {
return await this._get(type, id);
}
Which calls:
_get(type, id) {
return new Promise(async (resolve, reject) => {
var data = _json[id] = _json[id] || await this._api(type, id);
console.log(data)
if(isAsset(data)) {
resolve(data);
} else if(isEntry(data)) {
await this._scan(data);
resolve(data);
} else {
const error = 'Response is not entry/asset.';
console.log(error);
reject(error);
}
});
}
The API call is:
_api(type, id) {
return new Promise((resolve, reject) => {
Request('http://cdn.contentful.com/spaces/' + Constants.Contentful.SPACE + '/' + (!type || type === Constants.Contentful.ENTRY ? 'entries' : 'assets') + '/' + id + '?access_token=' + Constants.Contentful.PRODUCTION_TOKEN, (error, response, data) => {
if(error) {
console.log(error);
reject(error);
} else {
data = JSON.parse(data);
if(data.sys.type === Constants.Contentful.ERROR) {
console.log(data);
reject(data);
} else {
resolve(data);
}
}
});
});
}
When an entry is returned, it is scanned:
_scan(data) {
return new Promise((resolve, reject) => {
if(data && data.fields) {
const keys = Object.keys(data.fields);
keys.forEach(async (key, i) => {
var val = data.fields[key];
if(isLink(val)) {
var child = await this._get(val.sys.linkType.toUpperCase(), val.sys.id);
this._inject(data.fields, key, undefined, child);
} else if(isLinkArray(val)) {
var children = await* val.map(async (link) => await this._get(link.sys.linkType.toUpperCase(), link.sys.id));
children.forEach((child, index) => {
this._inject(data.fields, key, index, child);
});
} else {
await new Promise((resolve) => setTimeout(resolve, 0));
}
if(i === keys.length - 1) {
resolve();
}
});
} else {
const error = 'Required data is unavailable.';
console.log(error);
reject(error);
}
});
}
If link references are found, additional requests are made and then the resulting JSON is injected into the original JSON in place of the reference:
_inject(fields, key, index, data) {
if(isNaN(index)) {
fields[key] = data;
} else {
fields[key][index] = data;
}
}
Notice, I'm using async, await, and Promise's I believe in their intended manor. What ends up happening: The calls for referenced data (gets resulting of _scan) end up occurring after the original request is returned. This ends up providing incomplete data to the content template.
Additional information concerning my build setup:
npm#2.14.2
node#4.0.0
webpack#1.12.2
babel#5.8.34
babel-loader#5.4.0
I believe the issue is in your forEach call in _scan. For reference, see this passage in Taming the asynchronous beast with ES7:
However, if you try to use an async function, then you will get a more subtle bug:
let docs = [{}, {}, {}];
// WARNING: this won't work
docs.forEach(async function (doc, i) {
await db.post(doc);
console.log(i);
});
console.log('main loop done');
This will compile, but the problem is that this will print out:
main loop done
0
1
2
What's happening is that the main function is exiting early, because the await is actually in the sub-function. Furthermore, this will execute each promise concurrently, which is not what we intended.
The lesson is: be careful when you have any function inside your async function. The await will only pause its parent function, so check that it's doing what you actually think it's doing.
So each iteration of the forEach call is running concurrently; they're not executing one at a time. As soon as the one that matches the criteria i === keys.length - 1 finishes, the promise is resolved and _scan returns, even though other async functions called via forEach are still executing.
You would need to either change the forEach to a map to return an array of promises, which you can then await* from _scan (if you want to execute them all concurrently and then call something when they're all done), or execute them one-at-a-time if you want them to execute in sequence.
As a side note, if I'm reading them right, some of your async functions can be simplified a bit; remember that, while awaiting an async function call returns a value, simply calling it returns another promise, and returning a value from an async function is the same as returning a promise that resolves to that value in a non-async function. So, for example, _get can be:
async _get(type, id) {
var data = _json[id] = _json[id] || await this._api(type, id);
console.log(data)
if (isAsset(data)) {
return data;
} else if (isEntry(data)) {
await this._scan(data);
return data;
} else {
const error = 'Response is not entry/asset.';
console.log(error);
throw error;
}
}
Similarly, _scan could be (assuming you want the forEach bodies to execute concurrently):
async _scan(data) {
if (data && data.fields) {
const keys = Object.keys(data.fields);
const promises = keys.map(async (key, i) => {
var val = data.fields[key];
if (isLink(val)) {
var child = await this._get(val.sys.linkType.toUpperCase(), val.sys.id);
this._inject(data.fields, key, undefined, child);
} else if (isLinkArray(val)) {
var children = await* val.map(async (link) => await this._get(link.sys.linkType.toUpperCase(), link.sys.id));
children.forEach((child, index) => {
this._inject(data.fields, key, index, child);
});
} else {
await new Promise((resolve) => setTimeout(resolve, 0));
}
});
await* promises;
} else {
const error = 'Required data is unavailable.';
console.log(error);
throw error;
}
}
What would be the idiomatic way to do something like a while loop with promises. So:
do something
if the condition still stands do it again
repeat
then do something else.
dosomething.then(possilblydomoresomethings).then(finish)
I've done it this way I was wondering if there were any better/more idomatic ways?
var q = require('q');
var index = 1;
var useless = function(){
var currentIndex = index;
console.log(currentIndex)
var deferred = q.defer();
setTimeout(function(){
if(currentIndex > 10)
deferred.resolve(false);
else deferred.resolve(true);
},500);
return deferred.promise;
}
var control = function(cont){
var deferred = q.defer();
if(cont){
index = index + 1;
useless().then(control).then(function(){
deferred.resolve();
});
}
else deferred.resolve();
return deferred.promise;
}
var chain = useless().then(control).then(function(){console.log('done')});
Output:
1
2
3
4
5
6
7
8
9
10
11
done
Here's a reusable function that I think is pretty clear.
var Q = require("q");
// `condition` is a function that returns a boolean
// `body` is a function that returns a promise
// returns a promise for the completion of the loop
function promiseWhile(condition, body) {
var done = Q.defer();
function loop() {
// When the result of calling `condition` is no longer true, we are
// done.
if (!condition()) return done.resolve();
// Use `when`, in case `body` does not return a promise.
// When it completes loop again otherwise, if it fails, reject the
// done promise
Q.when(body(), loop, done.reject);
}
// Start running the loop in the next tick so that this function is
// completely async. It would be unexpected if `body` was called
// synchronously the first time.
Q.nextTick(loop);
// The promise
return done.promise;
}
// Usage
var index = 1;
promiseWhile(function () { return index <= 11; }, function () {
console.log(index);
index++;
return Q.delay(500); // arbitrary async
}).then(function () {
console.log("done");
}).done();
This is the simplest way I've found to express the basic pattern: you define a function that calls the promise, checks its result, and then either calls itself again or terminates.
const doSomething = value =>
new Promise(resolve =>
setTimeout(() => resolve(value >= 5 ? 'ok': 'no'), 1000))
const loop = value =>
doSomething(value).then(result => {
console.log(value)
if (result === 'ok') {
console.log('yay')
} else {
return loop(value + 1)
}
})
loop(1).then(() => console.log('all done!'))
See it in action on JSBin
If you were using a promise that resolves or rejects, you would define then and catch instead of using an if-clause.
If you had an array of promises, you would just change loop to shift or pop the next one each time.
EDIT: Here's a version that uses async/await, because it's 2018:
const loop = async value => {
let result = null
while (result != 'ok') {
console.log(value)
result = await doSomething(value)
value = value + 1
}
console.log('yay')
}
See it in action on CodePen
As you can see, it uses a normal while loop and no recursion.
I'd use an object to wrap the value. That way you can have a done property to let the loop know you're done.
// fn should return an object like
// {
// done: false,
// value: foo
// }
function loop(promise, fn) {
return promise.then(fn).then(function (wrapper) {
return !wrapper.done ? loop(Q(wrapper.value), fn) : wrapper.value;
});
}
loop(Q.resolve(1), function (i) {
console.log(i);
return {
done: i > 10,
value: i++
};
}).done(function () {
console.log('done');
});
This is for bluebird not q but since you didn't mention q specifically.. in the bluebird api doc the author mentions returning a promise-generating function would be more idiomatic than using deferreds.
var Promise = require('bluebird');
var i = 0;
var counter = Promise.method(function(){
return i++;
})
function getAll(max, results){
var results = results || [];
return counter().then(function(result){
results.push(result);
return (result < max) ? getAll(max, results) : results
})
}
getAll(10).then(function(data){
console.log(data);
})
Since I can't comment on Stuart K's answer I'll add a little bit here. Based on Stuart K's answer you can boil it down to a surprisingly simple concept: Reuse an unfulfilled promise. What he has is essentially:
Create a new instance of a deferred promise
Define your function that you want to call in a loop
Inside that function:
Check to see if you're done; and when you are resolve the promise created in #1 and return it.
If you are not done then tell Q to use the existing promise and run the unfullfilled function that is the "recursive" function, or fail if it died. Q.when(promise, yourFunction, failFunction)
After defining your function use Q to trigger the function for the first time using Q.nextTick(yourFunction)
Finally return your new promise to the caller (which will trigger the whole thing to start).
Stuart's answer is for a more generic solution, but the basics are awesome (once you realize how it works).
This pattern is now more easily called by using q-flow. An example, for the above problem:
var q = require('q');
require('q-flow');
var index = 1;
q.until(function() {
return q.delay(500).then(function() {
console.log(index++);
return index > 10;
});
}).done(function() {
return console.log('done');
});
Here is an extensions to the Promise prototype to mimic the behavior of a for loop. It supports promises or immediate values for the initialization, condition, loop body, and increment sections. It also has full support for exceptions, and it does not have memory leaks. An example is given below on how to use it.
var Promise = require('promise');
// Promise.loop([properties: object]): Promise()
//
// Execute a loop based on promises. Object 'properties' is an optional
// argument with the following fields:
//
// initialization: function(): Promise() | any, optional
//
// Function executed as part of the initialization of the loop. If
// it returns a promise, the loop will not begin to execute until
// it is resolved.
//
// Any exception occurring in this function will finish the loop
// with a rejected promise. Similarly, if this function returns a
// promise, and this promise is reject, the loop finishes right
// away with a rejected promise.
//
// condition: function(): Promise(result: bool) | bool, optional
//
// Condition evaluated in the beginning of each iteration of the
// loop. The function should return a boolean value, or a promise
// object that resolves with a boolean data value.
//
// Any exception occurring during the evaluation of the condition
// will finish the loop with a rejected promise. Similarly, it this
// function returns a promise, and this promise is rejected, the
// loop finishes right away with a rejected promise.
//
// If no condition function is provided, an infinite loop is
// executed.
//
// body: function(): Promise() | any, optional
//
// Function acting as the body of the loop. If it returns a
// promise, the loop will not proceed until this promise is
// resolved.
//
// Any exception occurring in this function will finish the loop
// with a rejected promise. Similarly, if this function returns a
// promise, and this promise is reject, the loop finishes right
// away with a rejected promise.
//
// increment: function(): Promise() | any, optional
//
// Function executed at the end of each iteration of the loop. If
// it returns a promise, the condition of the loop will not be
// evaluated again until this promise is resolved.
//
// Any exception occurring in this function will finish the loop
// with a rejected promise. Similarly, if this function returns a
// promise, and this promise is reject, the loop finishes right
// away with a rejected promise.
//
Promise.loop = function(properties)
{
// Default values
properties = properties || {};
properties.initialization = properties.initialization || function() { };
properties.condition = properties.condition || function() { return true; };
properties.body = properties.body || function() { };
properties.increment = properties.increment || function() { };
// Start
return new Promise(function(resolve, reject)
{
var runInitialization = function()
{
Promise.resolve().then(function()
{
return properties.initialization();
})
.then(function()
{
process.nextTick(runCondition);
})
.catch(function(error)
{
reject(error);
});
}
var runCondition = function()
{
Promise.resolve().then(function()
{
return properties.condition();
})
.then(function(result)
{
if (result)
process.nextTick(runBody);
else
resolve();
})
.catch(function(error)
{
reject(error);
});
}
var runBody = function()
{
Promise.resolve().then(function()
{
return properties.body();
})
.then(function()
{
process.nextTick(runIncrement);
})
.catch(function(error)
{
reject(error);
});
}
var runIncrement = function()
{
Promise.resolve().then(function()
{
return properties.increment();
})
.then(function()
{
process.nextTick(runCondition);
})
.catch(function(error)
{
reject(error);
});
}
// Start running initialization
process.nextTick(runInitialization);
});
}
// Promise.delay(time: double): Promise()
//
// Returns a promise that resolves after the given delay in seconds.
//
Promise.delay = function(time)
{
return new Promise(function(resolve)
{
setTimeout(resolve, time * 1000);
});
}
// Example
var i;
Promise.loop({
initialization: function()
{
i = 2;
},
condition: function()
{
return i < 6;
},
body: function()
{
// Print "i"
console.log(i);
// Exception when 5 is reached
if (i == 5)
throw Error('Value of "i" reached 5');
// Wait 1 second
return Promise.delay(1);
},
increment: function()
{
i++;
}
})
.then(function()
{
console.log('LOOP FINISHED');
})
.catch(function(error)
{
console.log('EXPECTED ERROR:', error.message);
});
Here is a generic solution that uses ES6 promises:
/**
* Simulates a while loop where the condition is determined by the result of a Promise.
*
* #param {Function} condition
* #param {Function} action
* #returns {Promise}
*/
function promiseWhile (condition, action) {
return new Promise((resolve, reject) => {
const loop = function () {
if (!condition()) {
resolve();
} else {
Promise.resolve(action())
.then(loop)
.catch(reject);
}
}
loop();
})
}
/**
* Simulates a do-while loop where the condition is determined by the result of a Promise.
*
* #param {Function} condition
* #param {Function} action
* #returns {Promise}
*/
function promiseDoWhile (condition, action) {
return Promise.resolve(action())
.then(() => promiseWhile(condition, action));
}
export default promiseWhile;
export {promiseWhile, promiseDoWhile};
And you can use it like this:
let myCounter = 0;
function myAsyncFunction () {
return new Promise(resolve => {
setTimeout(() => {
console.log(++myCounter);
resolve()
}, 1000)
});
}
promiseWhile(() => myCounter < 5, myAsyncFunction).then(() => console.log(`Timer completed: ${myCounter}`));
var Q = require('q')
var vetor = ['a','b','c']
function imprimeValor(elements,initValue,defer){
console.log( elements[initValue++] )
defer.resolve(initValue)
return defer.promise
}
function Qloop(initValue, elements,defer){
Q.when( imprimeValor(elements, initValue, Q.defer()), function(initValue){
if(initValue===elements.length){
defer.resolve()
}else{
defer.resolve( Qloop(initValue,elements, Q.defer()) )
}
}, function(err){
defer.reject(err)
})
return defer.promise
}
Qloop(0, vetor,Q.defer())
I am now using this:
function each(arr, work) {
function loop(arr, i) {
return new Promise(function(resolve, reject) {
if (i >= arr.length) {resolve();}
else try {
Promise.resolve(work(arr[i], i)).then(function() {
resolve(loop(arr, i+1))
}).catch(reject);
} catch(e) {reject(e);}
});
}
return loop(arr, 0);
}
This accepts an array arr and a function work and returns a Promise. The supplied function gets called once for each element in the array and gets passed the current element and it's index in the array. It may be sync or async, in which case it must return a Promise.
You can use it like this:
var items = ['Hello', 'cool', 'world'];
each(items, function(item, idx) {
// this could simply be sync, but can also be async
// in which case it must return a Promise
return new Promise(function(resolve){
// use setTimeout to make this async
setTimeout(function(){
console.info(item, idx);
resolve();
}, 1000);
});
})
.then(function(){
console.info('DONE');
})
.catch(function(error){
console.error('Failed', error);
})
Each item in the array will be handled in turn. Once all are handled, the code given to .then() will run, or, if some error occurred, the code given to .catch(). Inside the work function, you can throw an Error (in case of synchronous functions) or reject the Promise (in case of async functions) to abort the loop.
function each(arr, work) {
function loop(arr, i) {
return new Promise(function(resolve, reject) {
if (i >= arr.length) {resolve();}
else try {
Promise.resolve(work(arr[i], i)).then(function() {
resolve(loop(arr, i+1))
}).catch(reject);
} catch(e) {reject(e);}
});
}
return loop(arr, 0);
}
var items = ['Hello', 'cool', 'world'];
each(items, function(item, idx) {
// this could simply be sync, but can also be async
// in which case it must return a Promise
return new Promise(function(resolve){
// use setTimeout to make this async
setTimeout(function(){
console.info(item, idx);
resolve();
}, 1000);
});
})
.then(function(){
console.info('DONE');
})
.catch(function(error){
console.error('Failed', error);
})
Using the ES6 Promise, I came up with this. It chains the promises and returns a promise. It's not technically a while loop, but does show how to iterate over promises synchronously.
function chain_promises(list, fun) {
return list.reduce(
function (promise, element) {
return promise.then(function () {
// I only needed to kick off some side-effects. If you need to get
// a list back, you would append to it here. Or maybe use
// Array.map instead of Array.reduce.
fun(element);
});
},
// An initial promise just starts things off.
Promise.resolve(true)
);
}
// To test it...
function test_function (element) {
return new Promise(function (pass, _fail) {
console.log('Processing ' + element);
pass(true);
});
}
chain_promises([1, 2, 3, 4, 5], test_function).then(function () {
console.log('Done.');
});
Here's my fiddle.
I thought I might as well throw my hat in the ring, using ES6 Promises...
function until_success(executor){
var before_retry = undefined;
var outer_executor = function(succeed, reject){
var rejection_handler = function(err){
if(before_retry){
try {
var pre_retry_result = before_retry(err);
if(pre_retry_result)
return succeed(pre_retry_result);
} catch (pre_retry_error){
return reject(pre_retry_error);
}
}
return new Promise(executor).then(succeed, rejection_handler);
}
return new Promise(executor).then(succeed, rejection_handler);
}
var outer_promise = new Promise(outer_executor);
outer_promise.before_retry = function(func){
before_retry = func;
return outer_promise;
}
return outer_promise;
}
The executor argument is the same as that passed to a Promise constructor, but will be called repeatedly until it triggers the success callback. The before_retry function allows for custom error handling on the failed attempts. If it returns a truthy value it will be considered a form of success and the "loop" will end, with that truthy as the result. If no before_retry function is registered, or it returns a falsey value, then the loop will run for another iteration. The third option is that the before_retry function throws an error itself. If this happens, then the "loop" will end, passing that error as an error.
Here is an example:
var counter = 0;
function task(succ, reject){
setTimeout(function(){
if(++counter < 5)
reject(counter + " is too small!!");
else
succ(counter + " is just right");
}, 500); // simulated async task
}
until_success(task)
.before_retry(function(err){
console.log("failed attempt: " + err);
// Option 0: return falsey value and move on to next attempt
// return
// Option 1: uncomment to get early success..
//if(err === "3 is too small!!")
// return "3 is sort of ok";
// Option 2: uncomment to get complete failure..
//if(err === "3 is too small!!")
// throw "3rd time, very unlucky";
}).then(function(val){
console.log("finally, success: " + val);
}).catch(function(err){
console.log("it didn't end well: " + err);
})
Output for option 0:
failed attempt: 1 is too small!!
failed attempt: 2 is too small!!
failed attempt: 3 is too small!!
failed attempt: 4 is too small!!
finally, success: 5 is just right
Output for option 1:
failed attempt: 1 is too small!!
failed attempt: 2 is too small!!
failed attempt: 3 is too small!!
finally, success: 3 is sort of ok
Output for option 2:
failed attempt: 1 is too small!!
failed attempt: 2 is too small!!
failed attempt: 3 is too small!!
it didn't end well: 3rd time, very unlucky
Lots of answers here and what you are trying to achieve is not very practical. but this should work. This was implemented in an aws lambda function, with Node.js 10 it will go until function timeout. It may also consume a decent amount of memory.
exports.handler = async (event) => {
let res = null;
while (true) {
try{
res = await dopromise();
}catch(err){
res = err;
}
console.log(res);
}//infinite will time out
};
function dopromise(){
return new Promise((resolve, reject) => {
//do some logic
//if error reject
//reject('failed');
resolve('success');
});
}
Tested on lambda and running fine for over 5 min. But as stated by others this is not a good thing to do.
I wrote a module which helps you do chained loops of asynchronous tasks with promises, it is based on the answer above provided by juandopazo
/**
* Should loop over a task function which returns a "wrapper" object
* until wrapper.done is true. A seed value wrapper.seed is propagated to the
* next run of the loop.
*
* todo/maybe? Reject if wrapper is not an object with done and seed keys.
*
* #param {Promise|*} seed
* #param {Function} taskFn
*
* #returns {Promise.<*>}
*/
function seedLoop(seed, taskFn) {
const seedPromise = Promise.resolve(seed);
return seedPromise
.then(taskFn)
.then((wrapper) => {
if (wrapper.done) {
return wrapper.seed;
}
return seedLoop(wrapper.seed, taskFn);
});
}
// A super simple example of counting to ten, which doesn't even
// do anything asynchronous, but if it did, it should resolve to
// a promise that returns the { done, seed } wrapper object for the
// next call of the countToTen task function.
function countToTen(count) {
const done = count > 10;
const seed = done ? count : count + 1;
return {done, seed};
}
seedLoop(1, countToTen).then((result) => {
console.log(result); // 11, the first value which was over 10.
});
https://github.com/CascadeEnergy/promise-seedloop