alphadogg
alphadogg

Reputation: 12900

Dispatch Action Inside A Promise Then Function In A Saga

I have a saga (using redux-saga) that calls a function that POSTs to an API endpoint (using axios). The deeper API call through axios returns a promise. I'd like to dispatch actions inside the then() method of the promise, but I obviously can't use a yield. A put() doesn't seem to put anything. What's the right way to do this?

Here's the saga:

export function* loginFlow(action) {
  try {
    const {username, password} = action.payload;
    const responsePromise = yield call(login, {username, password, isRegistering: false});
    yield responsePromise
            .then(result => {
              console.log('loginFlow responsePromise result', result);
              put(creators.queueLoginSucceededAction()); // doesn't work
              put(push('/')); // doesn't work
            })
            .catch(err => {
              console.log('loginFlow responsePromise err', err);
              put(creators.queueLoginFailedAction()); // doesn't work
            });
  }
  catch(err) {
    console.log(err);
    yield put(creators.queueLoginFailedAction());
  }
}

Here's the function being called:

export function* login(options) {
  try {
    // if we are already logged in, via token in local storage,
    // then skip checking against server
    if( store.get('token') ) {
      return Promise.resolve('Already logged in.');
    }

    // query server for valid login, returns a JWT token to store
    const hash = yield bcrypt.hashSync(options.password, 10);
    yield put(creators.queueLoginHttpPostedAction());
    return axios.post('/auth/local', {
              params: {
                username: options.username,
                password: hash,
                hash:     true,
              }
            })
            .then(result => {
              console.log('api>auth>login>result', result);
              put(creators.queueLoginHttpSucceededAction()); // doesn't work
              return Promise.resolve('Login successful');
            })
            .catch(err => {
              console.log('api>auth>login>err', err);
              put(creators.queueLoginHttpFailedAction()); // doesn't work
              return Promise.reject(err.message);
            });
  }
  catch (err) {
    yield put(creators.queueLoginHttpFailedAction());
    return Promise.reject('Login could not execute');
  }
}

Upvotes: 1

Views: 3623

Answers (1)

adomnom
adomnom

Reputation: 574

A saga 'yield call' will wait for a returned promise to complete. If it fails, it throws an error, so instead of using 'then' and 'catch' for promises, you can just use a normal try-catch instead.

The Redux Saga docs explain this in more detail - definitely worth a read:

https://redux-saga.js.org/docs/basics/ErrorHandling.html

In other words, the login(options) function below will execute the HTTP request and so long as it doesn't send back a rejected promise, it will continue to queue the succeeded action and redirect the user back. If it does send back a rejected promise, it will instead immediately jump to the 'catch' block instead.

Corrected for the login flow:

export function * loginFlow(action) {
  try {
    const {username, password} = action.payload;
    const responsePromise = yield call(login, {username, password, isRegistering: false});
    console.log('loginFlow responsePromise result', result);
    yield put(creators.queueLoginSucceededAction());
    yield put(push('/'));
  }
  catch(err) {
    console.log('loginFlow responsePromise err', err);
    yield put(creators.queueLoginFailedAction());
  }
}

And for the actual login process:

export function * login(options) {
  // if we are already logged in, via token in local storage,
  // then skip checking against server
  if( store.get('token') ) {
    return Promise.resolve('Already logged in.');
  }

  // query server for valid login, returns a JWT token to store
  const hash = yield bcrypt.hashSync(options.password, 10);
  yield put(creators.queueLoginHttpPostedAction());

  try {
    yield call(
      axios.post, 
      '/auth/local', 
      { params: { username: options.username, password: hash, hash: true } }
    )

    console.log('api>auth>login>result', result);
    yield put(creators.queueLoginHttpSucceededAction()); // doesn't work
    return Promise.resolve('Login successful');

  } catch(err) {
    console.log('api>auth>login>err', err);
    yield put(creators.queueLoginHttpFailedAction()); // doesn't work
    return Promise.reject(err.message);
  }
}

Upvotes: 2

Related Questions