I'm looking for the best URL (say a local cache) at a set of URLs: ['https://1.2.3.4', 'https://httpbin.org/delay/3', 'https://httpbin.org/status/500'] and choose the best working one with a 5 second timeout. Else fallback to 'https://httpbin.org/status/200'. In this contrived example, 'https://httpbin.org/delay/3' should win!
function fallback(response) {
if (response.ok) {
return response;
}
console.log("Trying fallback")
return fetch("https://httpstat.us/200") // absolute fallback
}
var p = Promise.race([
fetch('https://1.2.3.4'), // will fail
fetch('https://httpbin.org/delay/3'), // should be the winner
fetch('https://httpbin.org/status/500'), // will fail
new Promise(function(resolve, reject) { // Competing with a timeout
setTimeout(() => reject(new Error('request timeout')), 5000)
})
])
.then(fallback, fallback)
.then(console.log)
The code I have has the problem whereby the Promise race ends when any returns. How can I best construct this so that 'https://httpbin.org/delay/3' will correctly win the race?
Promise.race([
new Promise(resolve => {
const ignoreError = () => {}
// first to resolve with .ok gets to resolve the promise
const resolveIfOk = res => res.ok && resolve(res)
fetch('https://httpbin.org/delay/20').then(resolveIfOk, ignoreError)
fetch('https://httpbin.org/delay/3').then(resolveIfOk, ignoreError)
fetch('https://1.2.3.4').then(resolveIfOk, ignoreError)
return;
}),
new Promise((_, reject) => setTimeout(() => reject(new Error('timed out')), 4000)) // reject if timeout
]).then(function(value) {
console.log("Success", value);
return value
}, function(reason) {
console.log("Going to use fallback", reason);
return fetch('https://httpstat.us/200')
}).then(console.log)
The solution comes via https://twitter.com/secoif
function race(promises) {
return Promise.race(Object.keys(promises).map((key) => {
return promises[key]
}))
.then(function(response) {
console.log(response);
if (!response.ok) {
delete promises[response.url];
return race(promises)
} else if (Object.keys(promises).length == 0 || response.url == 'timeout') {
console.log('returning default');
return fetch("https://httpstat.us/200")
}
return response;
})
}
race({
'https://1.2.3.4': fetch('https://1.2.3.4'),
'https://1.2.3.4': fetch('https://1.2.3.4'),
'timeout': new Promise(function(resolve, reject) {
setTimeout(() => reject(new Error('request timeout')), 5000)
})
}).then(function(response) {
console.log(response)
}).catch(function(err) {
console.log('err:');
console.log(err.message)
return fetch("https://httpstat.us/200")
}).then(function(defaultFetch) {
console.log('timed out requesting failed urls, getting default:');
console.log(defaultFetch);
});
Related
i have a promise chain
If i receive error in getServiceCost I want to repeat the chain again (retry) for 2 times how can i achieve this when using Promise chain , meaning again execute getUser, getServiceCost
getUser(100)
.then(getServices)
.then(getServiceCost)
.then(console.log);
function getUser(userId) {
return new Promise((resolve, reject) => {
console.log('Get the user from the database.');
setTimeout(() => {
resolve({
userId: userId,
username: 'admin'
});
}, 1000);
})
}
function getServices(user) {
return new Promise((resolve, reject) => {
console.log(`Get the services of ${user.username} from the API.`);
setTimeout(() => {
resolve(['Email', 'VPN', 'CDN']);
}, 3 * 1000);
});
}
function getServiceCost(services) {
return new Promise((resolve, reject) => {
console.log(`Calculate the service cost of ${services}.`);
setTimeout(() => {
resolve(services.length * 100);
}, 2 * 1000);
});
}
If i receive error in getServiceCost I want to repeat the chain again (retry) for 2 times how can i achieve this when using Promise chain , meaning again execute
getUser, getServiceCost
I'd use an async function (all modern environments support them, and you can transpile for obsolete environments), which lets you use a simple loop. Perhaps as a utility function you can reuse:
async function callWithRetry(fn, retries = 3) {
while (retries-- > 0) {
try {
return await fn();
} catch (error) {
if (retries === 0) {
throw error;
}
}
}
return new Error(`Out of retries`); // Probably using an `Error` subclass
}
Using it:
callWithRetry(() => getUser(100).then(getServices).then(getServiceCost))
.then(console.log)
.catch(error => { /*...handle/report error...*/ });
Or
callWithRetry(async () => {
const user = await getUser(100);
const services = await getServices(user);
return await getServiceCost(services);
})
.then(console.log)
.catch(error => { /*...handle/report error...*/ });
im trying to write a promise but seems to be missing something. here is my code:
const myPromise = new Promise(() => {
setTimeout(() => {
console.log("getting here");
return setinputs({ ...inputs, images: imageAsUrl });
}, 100);
});
myPromise
.then(() => {
console.log("getting here too");
firebase.database().ref(`collection/${idNode}`).set(inputs);
})
.then(() => {
console.log("all is set");
})
.catch((err) => {
console.log(err);
});
if i run the program, the first part of the promise is executing but all .then() functions arent executing. how do i fix this?
In this scheme, the promise callback has one (resolve) or two (resolve,reject) arguments.
let p = new Promise((resolve, reject)=> {
//do something
//resolve the promise:
if (result === "ok") {
resolve(3);
}
else {
reject("Something is wrong");
}
});
p.then(res => {
console.log(res); // "3"
}).catch(err => {
console.error(err); //"Something is wrrong
});
Of course, nowadays you can use async + await in a lot of cases.
You need to resolve the promise, using resolve() and also return the promise from firebase so the next .then in the chain works properly.
const myPromise = new Promise((resolve, reject) => {
setTimeout(() => {
console.log("getting here");
// You have to call resolve for all `.then` methods to be triggered
resolve({ ...inputs, images: imageAsUrl });
}, 100);
});
myPromise
.then((inputs) => {
console.log("getting here too");
// You have to return a promise in a .then function for the next .then to work properly
return firebase.database().ref(`collection/${idNode}`).set(inputs);
})
.then(() => {
console.log("all is set");
})
.catch((err) => {
console.log(err);
});
I have this following piece of code
new Promise((resolve, reject) => {
resolve(apiRequest(data))
reject(console.log('Error'))
}).then(response)
Both methods (resolve and reject) are being fired but I want to call reject only when something goes wrong.
How can I throw an error if something goes wrong on that case?
I checked that but it seems like I can not use an If statement to do that check.
new Promise((resolve, reject) => {
const printResult = apiRequest(data)
console.log(printResult) //Outputs Promise {<pending>}
resolve(printResult) //Then it works
reject(console.log('Error'))
}).then(response)
What would be the correct approach to reject a promise?
The easiest way would be with an if condition. i.e
new Promise((resolve, reject) => {
// do something...
if(somethingGoodHappened) {
resolve(data)
} else {
reject(error)
}
})
But usually when dealing with async requests, the thing you are calling will often be returning a promise, so you can attach the then and catch callbacks there.
apiRequest(data)
.then((result) => {
// all good
})
.catch((err) => {
console.log(err)
})
const mock_api = () => new Promise((res, rej) => {
const number = Math.floor((Math.random() * 100) + 1);
setTimeout(() => {
if (number%2==0) return res('randomly RESOLVED')
return rej('randomly REJECTED')
}, 2000)
})
const async_promise = () => new Promise(async (resolve, reject) => {
try {
const resolvedPromise = await mock_api()
resolve(resolvedPromise)
} catch (e) {
reject(e)
}
})
const classicPromise = () => new Promise((resolve, reject) => {
mock_api()
.then(resolve)
.catch(reject)
})
const makeAsyncRequest = async () => {
try {
const data = await async_promise()
console.log('ASYNC AWAIT RESOLVE', data)
} catch (e) {
console.log('ASYNC AWAIT ERR', e)
}
}
makeAsyncRequest()
classicPromise()
.then(r => console.log('PROMISE CHAIN RESOLVE', r))
.catch(e => console.log('PROMISE CHAIN ERR', e))
Because of you resolve before reject so it cannot run into reject,
You can use:
if (printResult) {
resolve(printResult)
} else {
reject(console.log('Error'))
}
You can catch exceptions and return them as rejected Promises
function asyncFunc() {
try {
doSomethingSync();
return doSomethingAsync()
.then(result => {
ยทยทยท
});
} catch (err) {
return Promise.reject(err);
}
}
Always check for err if there is any err return a promise (example below)
// Return new promise
return new Promise(function(resolve, reject) {
// Do async job
request.get(options, function(err, resp, body) {
if (err) {
reject(err);
} else {
resolve(JSON.parse(body));
}
})
})
I have a Promise.all that executes asynchronous functions mapped on an array input if it's not null and then resolve data to a previously defined Promise:
Promise.all((inputs || []).map(input => {
return new Promise((resolve, reject) => {
someAsyncFunc(input)
.then(intermediateOutput => {
someOtherAsyncFunc(intermediateOutput )
.then(output => {
return Promise.resolve(output )
})
.catch(reason=> {
return Promise.reject(reason)
})
})
.catch(reason => {
return Promise.reject(reason);
})
})
.then(outputs => {
resolve(outputs)
})
.catch(reason => {
reject(reason)
})
}))
I only get empty outputs before even someAsyncFunc finishes its work. How can make Promise.all wait for the promises inside to finish their asynchronous work ?
Would not just
return Promise.all((inputs || []).map(input =>
somePromiseFunc(input).then(someOtherPromiseFunc)
);
work ?
You're not using Promise.all right the first time since it takes an array of promises as input, and not (resolve, reject) => { ... }
Promise.all is going to be rejected as soon as one of the underlying promises fails, so you don't need to try to do something around catch(error => reject(error)
Example:
const somePromiseFunc = (input) => new Promise((resolve, reject) => {
setTimeout(() => {
if (input === 0) { reject(new Error('input is 0')); }
resolve(input + 1);
}, 1000);
});
const someOtherPromiseFunc = (intermediateOutput) => new Promise((resolve, reject) => {
setTimeout(() => {
if (intermediateOutput === 0) { reject(new Error('intermediateOutput is 0')); }
resolve(intermediateOutput + 1);
}, 1000);
});
const f = inputs => {
const t0 = Date.now()
return Promise.all((inputs || []).map(input => somePromiseFunc(input).then(someOtherPromiseFunc)))
.then(res => console.log(`result: ${JSON.stringify(res)} (after ${Date.now() - t0}ms)`))
.catch(e => console.log(`error: ${e} (after ${Date.now() - t0}ms)`));
};
f(null)
// result: [] (after 0ms)
f([1, 0])
// error: Error: input is 0 (after 1001ms)
f([1, -1])
// error: Error: intermediateOutput is 0 (after 2002ms)
f([1, 2])
// result: [3,4] (after 2002ms)
See jfriend's comment.
someAsyncFunc and someOtherAsyncFunc are function that properly return a promise
with something like return new Promise(/*...*/);
this is useless:
.then(output => {
return Promise.resolve(output )
})
read the Promise documentation
same
.catch(reason=> {
return Promise.reject(reason)
})
the Promise is already rejecting, you don't need to catch and reject yourself
to make sure Promises are chainable you need to return the Promise
// ...
return new Promise((resolve, reject) => {
if(inputs == null)
resolve([]);
else {
Promise.all(inputs.map(input => {
return someAsyncFunc(input)
.then(someOtherAsyncFunc)
}))
.then(resolve)
.catch(reject)
}
});
note I would rather not make the arry for Promise.all inline, it adds visual clutter:
return new Promise((resolve, reject) => {
if(inputs == null)
resolve([]);
else {
const myPromises = inputs.map(input => {
return someAsyncFunc(input)
.then(someOtherAsyncFunc)
});
Promise.all(myPromises)
.then(resolve)
.catch(reject)
}
});
it may still fail if you made other mistakes.
I would like to create a function that returns a promise and if something throws an error within, it returns promise reject.
function promiseFunc(options) {
return new Promise(() => {
return options;
});
}
function myfunc(options) {
return new Promise(() => {
if (!options) throw new Error("missing options");
return promiseFunc(options).then((result) => {
if (result.throwerr) throw new Error("thrown on purpose");
return result.value;
});
});
};
My test as follows:
const myfunc = require("./myfunc");
describe('myfunc', () => {
it('should fail without options', () => {
return myfunc()
.then((result) => { throw new Error(result) }, (err) => {
console.log("test #1 result:", err.message === "missing options");
});
});
it('should fail on options.throwerr', () => {
return myfunc({throwerr: true})
.then((result) => {}, (err) => {
console.log("test #2 result:", err.message === "thrown on purpose");
});
});
it('should return options.value', () => {
return myfunc({value: "some result", throwerr: false})
.then((result) => {
console.log("test #3 result:", result === "some result");
}, (err) => {});
});
});
The first test pass, but the second and third fails.
Log #2 does not even run, so I assumed the "throw on purpose" messes up something, therefore I created test #3, where I don't throw anything, but it still fails.
What am I missing?
Solution:
function promiseFunc(options) {
return new Promise(resolve => {
return resolve(options);
});
}
function myfunc(options) {
return new Promise((resolve, reject) => {
if (!options) throw new Error("missing options");
return promiseFunc(options).then(result => {
if (result.throwerr) throw new Error("thrown on purpose");
return resolve(result.value);
}).catch(err => {
return reject(err);
});
});
};
You forgot to pass a function with resolve and reject parameters, so your promises just don't work.
function promiseFunc(options) {
return new Promise(resolve => { // resolve function
resolve(options)
})
}
module.exports = function myfunc(options) {
return new Promise((resolve, reject) => { // since you may either resolve your promise or reject it, you need two params
if (!options) {
return reject(new Error("missing options"))
}
return promiseFunc(options).then(result => {
if (result.throwerr) {
return reject(new Error("thrown on purpose"))
}
resolve(result.value)
})
})
}
... and the test (mocha)
const assert = require('assert'),
myfunc = require("./myfunc")
describe('myfunc', () => {
it('should fail without options', done => { // mind the callback, promises are always async
myfunc()
.catch(err => {
assert(err.message === "missing options")
done() // <- called here
})
})
it('should fail on options.throwerr', done => {
myfunc({throwerr: true})
.catch(err => {
assert(err.message === "thrown on purpose")
done()
})
})
it('should return options.value', done => {
return myfunc({value: "some result", throwerr: false})
.then(result => {
assert(result === "some result")
done()
})
})
})
I would like to create a function that returns a promise and if something throws an error within, it returns promise reject.
This will do it ...
var q = require('q'); // In recent versions of node q is available by default and this line is not required
function iReturnAPromise(num) {
var def = q.defer();
if (typeof num=== 'number') {
try {
var value = 100 / num;
def.resolve(value);
} catch(e) {
def.reject("oops a division error - maybe you divided by zero");
}
} else {
def.reject("o no its not a number");
}
return def.promise;
}
PS this function was coded freehand and has not been tested - but this will work. Obviously try catch should be used sparingly.
PS I prefer the q library implementation of promise instead of the default node promise library - they take a very different approach. q dispenses with all the wrapping!
using the promise library u wanted ...
function iReturnAPromise(num) {
return new Promise(function(resolve, reject) {
if (typeof num === 'number') {
try {
var value = 100 / num;
resolve(value);
} catch (e) {
reject("oops a division error - maybe you divided by zero");
}
} else {
reject("o no its not a number");
}
})
}
iReturnAPromise(7).then(
function(response) {console.log("success", response)},
function(response) {console.log("failure", response)}
);
// Unexpectedly this is not an error in node 5.6 because div by 0 is not an error operation anymore!
iReturnAPromise(0).then(
function(response) {console.log("success", response)},
function(response) {console.log("failure", response)}
);
iReturnAPromise("fred").then(
function(response) {console.log("success", response)},
function(response) {console.log("failure", response)}
);
you can see why i prefer the q syntax :)