简体   繁体   中英

Wait for async method to finish

I am trying to create a method that calls a http request. If the request returns 401 error, I would like to relogin and try the request again. My method looks like this:

return this.http.post(url, this.cleanData(body), this.appendHttpHeaders(options))
        .map(response => response)
        .catch(error => {
            if (error.status === 401) {
                this.loginService.login(true).then(() => {
                    console.log("login should be finished: " + this.loginService.isLoggedIn());
                    return this.http.post(url, this.cleanData(body), this.appendHttpHeaders(options))
                        .map(response => response);
                })
            } else {
                this.errorReportingService.reportHttpError(error);
                return Observable.throw(error);
            }
        });

public login(forceLogin = false, loginCallback: () => any = null): Promise<String> {
    ...
    console.log(1);
    return new Promise((resolve, reject) => {
        console.log(2);
        this.popupLogin(loginCallback).then((result) => {
            console.log(11);
            resolve();
        })
    }).then(() => {
        return new Promise((resolve, reject) => {
            console.log(12);
            resolve();
        })
    })
}

private popupLogin(loginCallback: () => any = null): Promise<String> {
    ...
    console.log(3);
    return new Promise((resolve, reject) => {
        console.log(4);
        this.handleLoginPopupFeedback(loginPopupHandle, loginCallback).then((result)=> {
            console.log(9);
            resolve();
        })
    }).then(() => {
        return new Promise((resolve, reject) => {
            console.log(10);
            resolve("ok");
        })
    })
}


private handleLoginPopupFeedback(loginPopupHandle: Window, loginCallback: () => any = null): Promise<string> {
    ...
    } else {
        // ...popup is accessible, check values
        let loginSuccessElement = loginPopupContent.getElementById('trucareOAuthLoginSuccess');
        let loginFailureElement = loginPopupContent.getElementById('trucareOAuthLoginFailure');

        if (!loginPopupHandle.closed &&
            (loginSuccessElement === null || loginSuccessElement === undefined) &&
            (loginFailureElement === null || loginFailureElement === undefined)
        ) {

            console.log(5);
            return new Promise((resolve, reject) => {
                console.log(6);
                setTimeout(() => {
                    console.log(7);
                    this.handleLoginPopupFeedback(loginPopupHandle, loginCallback).then((result) => {
                        console.log(8);
                        resolve();
                    }, 500)
                })
            });
        }
        ...
    }

    return Promise.resolve("4");
}

Problem is that the login call is asynchronous. Is there a way to wait for loginService.login() to finish and then try request again?

EDIT: Modified the code according to the comments, but the call is still not repeated, even tho the console.log is logging. But I even don't see the call in networks sockets in browser

You could use the retryWhen() operator, which lets you wait until another observable emits to retry an initial observable in case of error, eg:

return this.http.post(url, body...)
  .retryWhen(errors => this.loginService.login(true))
  .map(res => res.json());

Just try retryWhen :

.retryWhen(errors => {
    return this.loginService.login(true).zip(errors).flatMap(([isLoggedIn, error]) => {
      if(!isLoggedIn || error.status !== 401) {
        return Observable.throw(error);
      }
      return Observable.of(true);
    })
  })

When I need to run and re-run a promise until a certain condition is met I use this function I wrote called ploop (promise loop):

// fn     : function that should return a promise.
// args   : the arguments that should be passed to fn.
// donefn : function that should check the result of the promise
//    and return true to indicate whether ploop should stop or not.
// promise: A promise value that is used internally by ploop and should never 
//    be passed in by the caller of ploop.
var ploop = function(fn, args, donefn, promise) {
    return (promise || Promise.resolve(true))    
      .then(function() {
          return(fn.apply(null, args));
      })
      .then(function(result) {
        var finished = donefn(result);
        if(finished === true){
           return result;
        } else {
          return ploop(fn, args, donefn, promise);
        }
    });
};

And here is an example using it:

// Function that returns a promise
var searchForNumber = function(number) {
    return new Promise(function(resolve, reject) {
      setTimeout(function() {
        var min = 1;
        var max = 10;
        var val = Math.floor(Math.random()*(max-min+1)+min);

        console.log('Value is: ' + val.toString());        

        return resolve(val);        
      }, 1000);
    });
};

var searchFor = 4;

var donefn = function(result) {
  return result == searchFor;
};

console.log('Searching for: ' + searchFor);
ploop(searchForNumber, [searchFor], donefn)
  .then(function(val) {
    console.log('Finally found! ' + val.toString());  
    process.exit(0);
  })
  .catch(function(err) {
    process.exit(1);
  });

Since your http.get returns a promise you should be able to pass it in to ploop along with your args. Your done function can check iterations or some other condition in the result to determine whether to keep running the promise or not.

Here is an example: http://jsbin.com/xefutos/4/edit?js,console

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM