Async Action Help Needed

Hello! I need help with the following straightforward use case in my project:

  1. Log the user in.
  2. Route them to the next page.

For logging the user in, I have the following async action defined in Vuex:

async loginUser({ commit }, credentials) {
  try {
    let userData = await login(credentials);
    let { id_token } = await getJWT();
    userData.id_token = id_token;
    commit('setUserData', userData);
  } catch (error) {
    log.error(error);
  }
}

And inside of my component, I’m utilizing this as follows:

methods: {
  ...mapActions('user', ['loginUser']),
  processLogin() {
    const { credentials, loginUser } = this;
    loginUser(credentials)
      .then(goToNextPage);
  }
}

My problem is this: the component’s goToNextPage method is executing before the loginUser action is complete. I thought that actions defined with async are thennable? I want goToNextPage to wait until loginUser is resolved.

I’ve looked over this syntax multiple times. Am I missing something?? There are no errors from the user management API, so there is a successful login happening.

Thanks in advance for your help!

Hi.

await directive must call a function which return promise with a resolve/reject directive. Is it the case here? in login and getJWT functions?

Take a look at the MDN Documentation, which contain a simple example.

Also I’m not an expert of asynchronous function – I lost myself in asynchronous hell weeks ago with a 6 levels of async/await call – but I’m not sure it can contain multiple “await” in same logic.

Yes – both login() and getJWT() are returning promises, so that part works fine.

And I guess nothing is catch in log.error (otherwise await is never be called)? or console?

As I told I’m not an expert and I’m not pretty comfortable with this two lines:

    const { credentials, loginUser } = this; // This is referring to local function (processLogin). So you bind a constant object with a function?
    loginUser(credentials) // This one because loginUser is waiting for two parameters: async loginUser({ commit }, credentials) 

The first line is using ES6 destructuring. The context this is the component, not the function.

The second line is calling an action which has an implicit first argument of state; the second argument is what you pass in.

May we have a look at login and getJWT function to see the entire logic of async/await?

They are both definitely returning promises. See below.

const login = params => {
  return new Promise(resolve => {
    Object.assign(params, {
      callback: resolve,
      include: ['id_token']
    });
    gigya.accounts.login(params);
  });
};

const getJWT = (params = { expiration: 315360000 }) => {
  return new Promise(resolve => {
    params.callback = resolve;
    gigya.accounts.getJWT(params);
  });
};

It returns promises, but you never resolve them!

I think there are two problems:
One: you never resolve the promise, so it never says “ok I’m done”.
Two: you must pass the then() command in a closure.

Look at this fiddle, based on MDN example, which is working with the closure and resolve().

The wrong way to do:

  1. without the resolve() command, .then() is never called.
  2. without closure in then(), function is called immediatly.

So this should work in your script:

methods: {
  ...mapActions('user', ['loginUser']),
  processLogin() {
    const { credentials, loginUser } = this;
    loginUser(credentials)
      .then(function() { goToNextPage(); });
  }
}

const login = params => {
  return new Promise(resolve => {
    Object.assign(params, {
      callback: resolve,
      include: ['id_token']
    });
    gigya.accounts.login(params);
    resolve();
  });
};

const getJWT = (params = { expiration: 315360000 }) => {
  return new Promise(resolve => {
    params.callback = resolve;
    gigya.accounts.getJWT(params);
    resolve();
  });
};

Hope this helps :slight_smile:

Always in the pengding state

I really appreciate the response, but that’s not it. They are getting resolved. The Gigya JS SDK calls whatever function you have under the callback key, and in this case it’s resolve. Those functions work just fine, which is why this is all so confusing.

Can you elaborate?

Hmmm… Assuming you added the closure in .then() call as said in my previous post, I read gigya documentation. In getJWT, callback parameters should be a string, not a function.

No. The SDK methods require you pass a function for callback. That’s how resolve is getting called, thus returning a promise in the proper format.

  • What does setUserData look like?
  • Try to replace login and getJWT with dummy promises like new Promise((r)=> setTimeout(()=>r({somedata}), 500)) and tell us about the result

:grimacing: Sorry, I’m just reading the documentation which says it as to be in string format:


It says it as to be string and and is required(and used by the API) only if the parameter “format” is set to “jsonp” (because json can’t call a callback method, unlike jsonp)

I don’t know what Gygia does, but if I had to use it and after reading the documentation, I probably set params with format as “jsonp” and callback as string format of “resolve”. As said in documentation.

Or let it as default but call manually with resolve(gigya.accounts.getJWT(params));

Maybe the problems comes from there: your callback is never called because format is as default: “json” and your resolve callback is never called?

I think it’s worth a try!

Not sure if this helps, but you have to be very careful with shallow copies and Vuex. I had some very strange errors occur due to Object.assign.

It is because if the source contains prop that does not exist in the target that prop won’t be reactive on the target. So the safe way is vm.object = Object.assign({}, vm.object, patchObject)

Yes sometimes that fixed it and for some cases I had to create deep copies.