最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - RxJs Observables: run retryWhen after some more async requests - Stack Overflow

programmeradmin3浏览0评论

My use case is:

  1. User requests asset from our API which fails because of JWT expiring (passed as an httpOnly cookie) - API returns a 401 status code.
  2. We go and authenticate them (without the user doing anything) again using a refresh_token to retrieve a new JWT with a request from our client to auth0.
  3. We send that new JWT to our API to be set as an httpOnly cookie to replace the expired one.
  4. We then want to retry the original request the user made to the API in step 1.

I'm trying to use Observables within my Redux app with redux-observable. If you can think of another way of making the above user flow work I would be happy to hear how.

NB. Im using rxjs V5

export const fetchAssetListEpic = (action$, store) => {
  return action$.ofType('FETCH_ASSET_LIST')
  .switchMap( action => {
    const options = {
      crossDomain: true,
      withCredentials: true,
      url: uriGenerator('assetList', action.payload)
    };
    return ajax(options);
  })
  .map(fetchAssetListSuccess)
  .retryWhen(handleError)
  .catch(redirectToSignIn);
};

function handleError(err) {
  return (err.status === 401) ? 
  /* Authenticate here [Step 2] */
  /* Send new JWT to API [Step 3] */
  /* If successful make original request again [Step 4] */
  :
  Observable.throw(err);
}
  
function redirectToSignIn() {
  /*I will redirect here*/
}

My use case is:

  1. User requests asset from our API which fails because of JWT expiring (passed as an httpOnly cookie) - API returns a 401 status code.
  2. We go and authenticate them (without the user doing anything) again using a refresh_token to retrieve a new JWT with a request from our client to auth0.
  3. We send that new JWT to our API to be set as an httpOnly cookie to replace the expired one.
  4. We then want to retry the original request the user made to the API in step 1.

I'm trying to use Observables within my Redux app with redux-observable. If you can think of another way of making the above user flow work I would be happy to hear how.

NB. Im using rxjs V5

export const fetchAssetListEpic = (action$, store) => {
  return action$.ofType('FETCH_ASSET_LIST')
  .switchMap( action => {
    const options = {
      crossDomain: true,
      withCredentials: true,
      url: uriGenerator('assetList', action.payload)
    };
    return ajax(options);
  })
  .map(fetchAssetListSuccess)
  .retryWhen(handleError)
  .catch(redirectToSignIn);
};

function handleError(err) {
  return (err.status === 401) ? 
  /* Authenticate here [Step 2] */
  /* Send new JWT to API [Step 3] */
  /* If successful make original request again [Step 4] */
  :
  Observable.throw(err);
}
  
function redirectToSignIn() {
  /*I will redirect here*/
}

So far I able to plete steps 1, 2 and 3 but not too sure of a way to add step 4. I may be pletely off the mark but any help would be great!

Share Improve this question asked Dec 1, 2016 at 9:23 JbargetJbarget 5691 gold badge5 silver badges15 bronze badges
Add a ment  | 

1 Answer 1

Reset to default 8

Well one thing you probably won't want to do is allow the error to make it to the top level stream. Even if you do a catch you have effectively killed the top level stream. So unless your redirect is doing a hard redirect instead of a a soft one via something like react-router, you won't be able to use this epic any more.

Thus I would say that you want most of the logic to be encapsulated within the switchMap:

function withAuthorizedFlow(source) {
  return source
    .map(fetchAssetListSuccess)
    // retryWhen takes a callback which accepts an Observable of errors
    // emitting a next causes a retry, while an error or plete will
    // stop retrying
    .retryWhen(e => e.flatMap(err => 
      Observable.if(
        // Returns the first stream if true, second if false
        () => err.status === 401,
        reauthenticate, // A stream that will emit once authenticated
        Observable.throw(err) // Rethrow the error
      ))
    )
    .catch(redirectToSignIn);
}

/** Within the epic **/
.switchMap(({payload}) => {
  const options = {
    crossDomain: true,
    withCredentials: true,
    url: uriGenerator('assetList', payload)
  };

  // Invoke the ajax request
  return ajax(options)
    // Attach a custom pipeline here
    // Not strictly necessary but it keeps this method clean looking.
    .let(withAuthorizedFlow);
})

The use of let above is pletely optional, I threw it in to clean up the function. Essentially though you want to contain the error to the inner stream so that it can't halt the outer one. I am not sure which ajax library you are using but you should also confirm that it will in fact return a cold Observable otherwise you will need to wrap it in a defer block to in order for the retryWhen to work.

发布评论

评论列表(0)

  1. 暂无评论