How to use js async/await in mutiple async requests

假如想象 提交于 2019-12-11 14:45:49

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!