I am just starting to learn how to use Promises as I have been experimenting with callbacks so far.
I am trying the code below:
It should invoke promise func1, wait 1 second, check func1's returned value, then invoke promise func2, wait 1 second and then check func2's returned value.
When I run it, I actually get this output:
In func1
func1 sent: 1
func2 sent: undefined
so it is clear the func2 is not being invoked.
Could someone assist a Promis newbie in his first code experiments?
Thank you
class A {
func1(param) {
return new Promise((resolve, reject) => {
// Use param...
console.log("In func1");
setTimeout(() => resolve("1"), 1000);
});
}
func2(param) {
return new Promise((resolve, reject) => {
// Use param...
console.log("In func2");
setTimeout(() => resolve("2"), 1000);
});
}
}
const a = new A();
a.func1("arg1")
.then((value) => {
// Check the value of value returned from func1
console.log("func1 sent: " + value);
}).then((value) => {
// Check the value of value returned from func2
console.log("func2 sent: " + value);
});
I based my code on this. Here there is no explicit call to the 2nd function and it works... Why doesn't it work in solution?
new Promise(function(resolve, reject) {
console.log("In func1");
setTimeout(() => resolve(1), 1000);
}).then(function(value) {
console.log("func1 sent: " + value);
return new Promise((resolve, reject) => {
console.log("In func2");
setTimeout(() => resolve(2), 1000);
});
}).then(function(value) {
console.log("func2 sent: " + value);
});
Related
I'm not sure what's going on here. I have set up an API route in NextJS that returns before the data has been loaded. Can anyone point out any error here please?
I have this function that calls the data from makeRequest():
export async function getVendors() {
const vendors = await makeRequest(`Vendor.json`);
console.log({ vendors });
return vendors;
}
Then the route: /api/vendors.js
export default async (req, res) => {
const response = await getVendors();
return res.json(response);
};
And this is the makeRequest function:
const makeRequest = async (url) => {
// Get Auth Header
const axiosConfig = await getHeader();
// Intercept Rate Limited API Errors & Retry
api.interceptors.response.use(
function (response) {
return response;
},
async function (error) {
await new Promise(function (res) {
setTimeout(function () {
res();
}, 2000);
});
const originalRequest = error.config;
if (error.response.status === 401 && !originalRequest._retry) {
token[n] = null;
originalRequest._retry = true;
const refreshedHeader = await getHeader();
api.defaults.headers = refreshedHeader;
originalRequest.headers = refreshedHeader;
return Promise.resolve(api(originalRequest));
}
return Promise.reject(error);
}
);
// Call paginated API and return number of requests needed.
const getQueryCount = await api.get(url, axiosConfig).catch((error) => {
throw error;
});
const totalItems = parseInt(getQueryCount.data['#attributes'].count);
const queriesNeeded = Math.ceil(totalItems / 100);
// Loop through paginated API and push data to dataToReturn
const dataToReturn = [];
for (let i = 0; i < queriesNeeded; i++) {
setTimeout(async () => {
try {
const res = await api.get(`${url}?offset=${i * 100}`, axiosConfig);
console.log(`adding items ${i * 100} through ${(i + 1) * 100}`);
const { data } = res;
const arrayName = Object.keys(data)[1];
const selectedData = await data[arrayName];
selectedData.map((item) => {
dataToReturn.push(item);
});
if (i + 1 === queriesNeeded) {
console.log(dataToReturn);
return dataToReturn;
}
} catch (error) {
console.error(error);
}
}, 3000 * i);
}
};
The issue that I'm having is that getVendors() is returned before makeRequest() has finished getting the data.
Looks like your issue stems from your use of setTimeout. You're trying to return the data from inside the setTimeout call, and this won't work for a few reasons. So in this answer, I'll go over why I think it's not working as well as a potential solution for you.
setTimeout and the event loop
Take a look at this code snippet, what do you think will happen?
console.log('start')
setTimeout(() => console.log('timeout'), 1000)
console.log('end')
When you use setTimeout, the inner code is pulled out of the current event loop to run later. That's why end is logged before the timeout.
So when you use setTimeout to return the data, the function has already ended before the code inside the timeout even starts.
If you're new to the event loop, here's a really great talk: https://youtu.be/cCOL7MC4Pl0
returning inside setTimeout
However, there's another fundamental problem here. And it's that data returned inside of the setTimeout is the return value of the setTimeout function, not your parent function. Try running this, what do you think will happen?
const foo = () => {
setTimeout(() => {
return 'foo timeout'
}, 1000)
}
const bar = () => {
setTimeout(() => {
return 'bar timeout'
}, 1000)
return 'bar'
}
console.log(foo())
console.log(bar())
This is a result of a) the event loop mentioned above, and b) inside of the setTimeout, you're creating a new function with a new scope.
The solution
If you really need the setTimeout at the end, use a Promise. With a Promise, you can use the resolve parameter to resolve the outer promise from within the setTimeout.
const foo = () => {
return new Promise((resolve) => {
setTimeout(() => resolve('foo'), 1000)
})
}
const wrapper = async () => {
const returnedValue = await foo()
console.log(returnedValue)
}
wrapper()
Quick note
Since you're calling the setTimeout inside of an async function, you will likely want to move the setTimeout into it's own function. Otherwise, you are returning a nested promise.
// don't do this
const foo = async () => {
return new Promise((resolve) => resolve(true))
}
// because then the result is a promise
const result = await foo()
const trueResult = await result()
Is the following jQuery code equivalent to how it is done in ES6's way, or should the jQuery way be written differently?
// ES6:
new Promise((resolve, reject) => {
console.log("start");
setTimeout(() => resolve("a"), 2000);
}).then(s => {
console.log(s);
return new Promise((resolve, reject) => {
setTimeout(() => resolve("b"), 2000);
});
}).then(s => {
console.log(s);
return new Promise((resolve, reject) => {
setTimeout(() => resolve("c"), 2000);
});
}).then(s => {
console.log(s);
});
// jQuery:
let d = $.Deferred();
d.then(s => {
console.log(s);
let d = $.Deferred();
setTimeout(() => d.resolve("b"), 2000);
return d.promise();
}).then(s => {
console.log(s);
let d = $.Deferred();
setTimeout(() => d.resolve("c"), 2000);
return d.promise();
}).then(s => {
console.log(s);
})
console.log("start");
setTimeout(() => d.resolve("a"), 2000);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
It seems:
Not everything can be written in one statement in jQuery. The executor has to be moved out as separate statements in jQuery.
For jQuery, not only promise needs to be involved. The deferred needs to be used, because otherwise there is no way to resolve the promise.
In jQuery, the line return d.promise(); can just be return d;, but is it good to return the deferred? The code that receives the deferred can inadvertently resolve the promise, but if it is chained promises, can any code actually receive the deferred and inadvertently resolve the promise?
Per #Tomalak and #jfriend00's comment, it is found that jQuery's promises can be written this way, similar to ES6 promises:
// jQuery:
$.Deferred(d => {
console.log("start");
setTimeout(() => d.resolve("a"), 2000);
}).then(s => {
console.log(s);
return $.Deferred(d => {
setTimeout(() => d.resolve("b"), 2000);
});
}).then(s => {
console.log(s);
return $.Deferred(d => {
setTimeout(() => d.resolve("c"), 2000);
});
}).then(s => {
console.log(s);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
Instead of function(resolve, reject), it is function(deferred).
The jQuery.Deferred method can be passed an optional function, which is called just before the method returns and is passed the new deferred object as both the this object and as the first argument to the function.
The passed in deferred is the same as this only if it is a traditional function but not an arrow function. But I think it will be better to use the passed in deferred d instead of using this.
In this case, there is nothing easier to wait until the data is received, and then resolve the Promise:
// Using Promise resolve/reject
module.exports = () => {
return new Promise(async (resolve, reject) => {
let doc = await Document();
doc.on('data', async (data) => {
resolve(data);
});
})
}
But what do I do in this case?
// Using async/await
module.exports = async () => {
let doc = await Document();
doc.on('data', (data) => {
// ???
});
}
You still need the new Promise, you just should use it as the operand of an await inside the async function, not using an async function as the executor:
module.exports = async () => {
const doc = await Document();
return new Promise(resolve, reject) => {
doc.on('data', resolve);
});
};
However, I would recommend to use once instead of on so that the event handler is removed after the first occurrence of the event - the promise can be resolve only once anyway. Also if you have node v11.13.0 or higher you can just use the events.once method so that you don't have to build the promise yourself - and it also handles error events correctly:
const { once } = require('events');
module.exports = async () => {
const doc = await Document();
return once(doc, 'data');
};
This observable polls getPromise() function every second. After getPromise() function returns 3 promises it stops resolving them. How do I detect that getPromise() function hasn't resolve/rejected any promise for the past, let's say, 2 seconds, and call onError handler. I've tried making it work with timeout operator to no avail. Any ideas?
Rx.Observable.interval(1000)
.switchMap(() => Rx.Observable.fromPromise(getPromise()))
.subscribe(onValue, onError);
function onValue(value){
console.log('value: ', value);
}
function onError(error){
console.log('error: ', error);
}
var getPromise = (function(){
var counter = 3;
return function(){
return new Promise(function(resolve, reject){
if(counter > 0) resolve(1);
counter--;
})
}
})();
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.3.0/Rx.js"></script>
You can use the race operator that subscribes only to the first Observable that emits.
You said you want to call onError handler after 2 of inactivity. This contradicts with using switchMap which automatically unsubscribes when a new Observable is returned from its callback. So you might want to use exhaustMap instead. Also when you emit an error notification the chain unsubscribes and you'll never receive any other value. This means that you shouldn't emit the timeout as an error or use also the retry operator to automatically resubscribe (but this really depends on what you're trying to achieve).
This is you updated example that is just using the race() operator.
Rx.Observable.interval(1000)
.switchMap(() =>
Rx.Observable.race(
Rx.Observable.fromPromise(getPromise()),
Rx.Observable.timer(0, 1000).mapTo(42)
)
)
.subscribe(onValue, onError);
function onValue(value){
console.log('value: ', value);
}
function onError(error){
console.log('error: ', error);
}
var getPromise = (function(){
var counter = 3;
return function(){
return new Promise(function(resolve, reject){
if(counter > 0) resolve(1);
counter--;
})
}
})();
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.3.0/Rx.js"></script>
Edit: To send a single error notification after 2 seconds of inactivity.
Rx.Observable.interval(1000)
.switchMap(() => Rx.Observable.fromPromise(getPromise()))
.timeout(2000)
.subscribe(onValue, onError);
function onValue(value){
console.log('value: ', value);
}
function onError(error){
console.log('error: ', error);
}
var getPromise = (function(){
var counter = 3;
return function(){
return new Promise(function(resolve, reject){
if(counter > 0) resolve(1);
counter--;
})
}
})();
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.2.0/Rx.js"></script>
There's really a bug in 5.3.0 not directly in the timeout() operator but in scheduling async actions. https://github.com/ReactiveX/rxjs/pull/2580
Without the timeout() operator:
Rx.Observable.interval(1000)
.switchMap(() =>
Rx.Observable.race(
Rx.Observable.fromPromise(getPromise()),
Rx.Observable.timer(0, 2000).map(function(_) {
throw new Error('timeout');
})
)
)
.subscribe(onValue, onError);
I am trying to implement Google OAuth 2 with with redux saga.
I have a watcher in my saga listening for GOOGLE_AUTH action which then executes googleLogin
function *watchGoogleAuth() {
yield *takeLatest(GOOGLE_AUTH, googleLogin)
}
function *googleLogin() {
const id_token = yield call(GoogleSignIn);
console.log(id_token);
const response = yield call(HttpHelper, 'google_token', 'POST', id_token, null);
console.log(response);
}
The implementation for GoogleSignIn is in apis.js
export function GoogleSignIn() {
const GoogleAuth = window.gapi.auth2.getAuthInstance();
GoogleAuth.signIn({scope: 'profile email'})
.then(
(res) => {
const GoogleUser = GoogleAuth.currentUser.get();
return {
id_token: GoogleUser.getAuthResponse().id_token
};
},
(err) => {
console.log(err)
}
)
}
But saga doesn't seem to wait for the GoogleSignIn to complete. As soon as OAuth consent screen pops up, saga proceeds executing the console.log without waiting for google signin promise to return actual data.
Is there any better way to handle this situation? Thanks!
To expand on #HenrikR's answer, the generator will not wait unless it receives a promise.
export const GoogleSignIn = () => {
const GoogleAuth = window.gapi.auth2.getAuthInstance();
return new Promise((resolve, reject) => {
GoogleAuth.signIn({scope: 'profile email'})
.then(
(res) => {
const GoogleUser = GoogleAuth.currentUser.get();
resolve(GoogleUser.getAuthResponse().id_token);
},
(err) => {
reject(err);
}
);
});
};
Accordingly, you should wrap the yield statement in a try/catch. Simplified and somewhat lazy:
function *googleLogin() {
try {
const id_token = yield call(GoogleSignIn);
if (id_token) { /* Possibly with more checks and validations */
console.log(id_token);
const response = yield call(HttpHelper, 'google_token', 'POST', id_token, null);
console.log(response);
}
} catch (e) {
console.log(e);
}
}