问题
I'm using Angular 7, now there is a method(Angular guard CanActivate) that contains some nested http call methods, i need to return data after all nested http call finished.
As below code shows, only after getCurrentUser()
finished, then return result in canActivate()
, while now, it always return false because getCurrentUser()
haven't finished.
export class AuthGuard implements CanActivate{
constructor(private commonService: CommonService) {
}
async canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<boolean> {
console.log('======');
await this.getCurrentUser();
return this.hasAccess;
}
hasAccess: boolean = false;
async getCurrentUser(){
await this.commonService.getToken().subscribe(token => {
this.commonService.getCurrentUser(param, token).subscribe(o => {
if(o.success){
this.hasAccess = true;
}else {
window.location.href = '/forbidden.html';
}
}, error => {
console.log(error);
});
});
console.log("async");
}
}
you can see there are two async methods A,B should be await, and A,B are not parallel, i checked docs about Promise and async/await, didn't find solution.
As await should always follow async, how can i do to let canActivate()
return result after all the async http call finished?
+++Update
this.commonService.getToken()
and this.commonService.getCurrentUser(param, token)
are http call(HttpClient), i tried a lot of solutions but no result.
回答1:
The Promise.all() method is what you are looking for.
回答2:
You can use a combination of async await
and Promise.all
. In this way you can wait for all your async network request and when all requests have finished, perform some action.
A Promise.all()
takes an array of promises and wraps them into a single promise. And we already know some nice syntax for dealing with a single promise. We can await it.
For your understanding, take a look at this code sample:
let films = await Promise.all(
characterResponseJson.films.map(async filmUrl => {
let filmResponse = await fetch(filmUrl)
return filmResponse.json()
})
)
console.log(films)
I have quoted this example from this article, which can help you to figure out your solution
How to use async/await with map and Promise.all
Update: For your use case you can use like this:
async getCurrentUser(){
await this.commonService.getToken().subscribe(async token => {
await this.commonService.getCurrentUser(param, token).subscribe(o => {
if(o.success){
this.hasAccess = true;
}else {
window.location.href = '/forbidden.html';
}
}, error => {
console.log(error);
});
});
console.log("async");
}
回答3:
async
and await
are built on top of promises. Promise is a special object in javascript extensively used to avoid callback hell
Also try
catch
blocks are important while using async
and await
because we need to handle errors as well, incase API fails.
hasAccess: boolean;
canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise < boolean > {
this.getCurrentUser();
return this.hasAccess;
}
async getCurrentUser() {
try {
const output1 = await Promise.resolve(fetch(urlOfToken).then(res => res.json())) // if GET call, if POST you can postData('', {})
const currentUser = await this.postData(
`URL fetching current user`,
{
token: `access token from object ${output} `,
param: 'any other param'
}
);
// Check in currentUser Object whether response contains user or not
// If user exists set this.hasAccess = true;
// IF not set this.hasAccess = false;
} catch (error) {
// Error Handling
console.log(error);
}
}
// Courtesy MDN
async postData(url = '', data = {}) {
// Default options are marked with *
const response = await fetch(url, {
method: 'POST', // *GET, POST, PUT, DELETE, etc.
mode: 'cors', // no-cors, *cors, same-origin
cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached
credentials: 'same-origin', // include, *same-origin, omit
headers: {
'Content-Type': 'application/json'
// 'Content-Type': 'application/x-www-form-urlencoded',
},
redirect: 'follow', // manual, *follow, error
referrer: 'no-referrer', // no-referrer, *client
body: JSON.stringify(data) // body data type must match "Content-Type" header
});
return await response.json(); // parses JSON response into native JavaScript objects
}
Extra reference on how to use promises
along with async
and await
. Also on how to make parallel, sequence and race API calls
const urls = [
'https://jsonplaceholder.typicode.com/users',
'https://jsonplaceholder.typicode.com/albums',
'https://jsonplaceholder.typicode.com/posts'
];
// BASIC
Promise
.all(urls.map(url => {
return fetch(url).then(res => res.json())
}))
.then((results) => {
console.log(results[0]);
console.log(results[1]);
console.log(results[2]);
})
.catch(() => console.log('error!'));
// async await
// built atop of promises
// benefit is it is easier to make code read easier nothing more promises can get job done actually
const getData = async function () {
try {
const [users, albums, posts] = await Promise.all(urls.map(url => {
return fetch(url).then(res => res.json())
}));
console.log('users', users);
console.log('albums', albums);
console.log('posts', posts);
} catch (error) {
console.log('Oops!');
}
}
// for await of
const getData2 = async function () {
const arrayOfPromises = await urls.map(url => fetch(url));
for await (let request of arrayOfPromises) {
const response = await request.json();
console.log(response);
}
}
const a = () => promisify('a', 100); // I am making it asynchronous here ; Can be API call or any asynchronus task
const b = () => promisify('b', 1000);// I am making it asynchronous here ; Can be API call or any asynchronus task
const c = () => promisify('c', 5000);// I am making it asynchronous here ; Can be API call or any asynchronus task
const promisify = (item, delay) =>
new Promise((resolve) =>
setTimeout(() =>
resolve(item), delay));
// Parallel
async function parallel() {
const [output1, output2, output3] = await Promise.all([a(), b(), c()]);
return `parallel done right: ${output1} , ${output2} , ${output3}`;
}
// race
async function race() {
const output1 = await Promise.race([a(), b(), c()]);
return `race done right: ${output1}`;
}
// sequence
async function sequence() {
const output1 = await a();
const output2 = await b();
const output3 = await c();
return `sequenece done right: ${output1}, ${output2}, ${output3}`;
}
parallel().then(console.log);
race().then(console.log);
sequence().then(console.log);
回答4:
Refer to the above answers and other people's help, i updated my code and now it works.
My update is use new Promise()
in getToken(), getUser() instead await
it, Promise
has there status(pending,resolved,rejected), once status changed it won't be changed anyway, in that way, once Promise
's status changed to resloved, it won't be changed and Promise
will return it's value, else it will error if change to reject.
Attach my updated code as below:
canActivate:
async canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<boolean> {
console.log('======');
let token = await this.getToken();
// let hasAccess = await this.getUser(token);
return await this.getUser(token);
}
getToken() and getUser():
// return a Promise object and resolve(token)
getToken(){
return new Promise((resolve, reject)=>{
this.commonService.getToken().subscribe(token=>{
resolve(token)
})
})
}
getUser(token: any) {
return new Promise<boolean>((resolve, reject) => {
this.commonService.getCurrentUser(param, token).subscribe(o => {
if(o.success){
hasAccess = true;
}else {
window.location.href = '/forbidden.html';
}
resolve(hasAccess);
}, error => {
console.log(error);
resolve(hasAccess);
});
})
}
I'm not very familiar with async/await and Promise, so welcome correction error.
来源:https://stackoverflow.com/questions/58706052/how-to-use-js-async-await-in-mutiple-async-requests