Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Async/Await not Waiting for Promise to Resolve

It appears that my use of await is not behaving as I understood it to.

I thought that an async function and the await keyword could be used on a call that returns a promise (for instance navigator.mediaDevices.getUserMedia) and it would pause the function execution (like a generator function) until the promise resolved and then it would continue on with the function.

Even though I am awaiting the call it's returning right away and my console logs are happening out of order. Specifically the console log starting with "reducer" appears in the console before the one starting with "getter", when it should be the other way around/is the opposite of how (I thought) the stack trace should run.

Can anyone tell me what's going on here?

Async helper function getter:

const getLocalStream = async () => {
  try {
    const localStream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
    console.log('getter localStream', localStream);
    console.log('about to return')
    return localStream;
  }
  catch (err) {
    console.error(err);
  }
}

export default getLocalStream;

Reducer/store:

import getLocalStream from './getLocalStream';

const reducer = (state = {}, action) => {
  switch (action.type) {
    case 'SEND_CALL': {
      const { id } = action;
      const localStream =  getLocalStream();
      console.log('reducer localStream', localStream);
      const call = state.peer.call(id, localStream);
      return { ...state, call };
    }
    default: 
      return state;
  }
};

Console:

reducer localStream Promise {<pending>}
Uncaught TypeError: Failed to execute 'addStream' on 'RTCPeerConnection': parameter 1 is not of type 'MediaStream'
getter localStream MediaStream {id: "B6Foz1jheGvnMqjiqC4PFoOvUVahF04zigzm", active: true, onaddtrack: null, onremovetrack: null, onactive: null, …}
about to return
like image 513
Casey Avatar asked Feb 16 '18 02:02

Casey


3 Answers

Use redx-thunk. Since your reducer has to return a new state you cannot do asynchronous operations in the reducer.

You can do asynchronous operations in middle ware so you can either write middle ware yourself or use redux-thunk.

If you dispatch an action that is a function then thunk will execute that function with the dispatch and a getState function.

//create send call action
const createSendCall = id => 
  async dispatch => {
    const localStream =  await getLocalStream();
    console.log('reducer localStream', localStream);
    const call = state.peer.call(id, localStream);
    dispatch(createDoneSend(call));
  }
//create done send action
const createDoneSend = call => ({
  type:"DONE_SEND",
  call
})

//reducer, SEND_CALL never comes here because it's handled in middle ware
switch (action.type) {
  case 'DONE_SEND': {
    return { ...state, call:action.call };
  }
  default: 
    return state;

Thunk will simplify asynchronous middle ware or actions that need to dispatch multiple actions.

How redux works and how the middle ware works is explained here it is calling the reducer "application handler function" because I was teaching it as event store, not redux specific.

A reason why redux doesn't accept promises as state and just set the state when it resolves can be found here. When a pending filter request is replaced with a newer request from the user the pending filter will reject, this is what should happen but how would redux know the promise is supposed to reject? So if we have to resolve then how do we tell redux not to set the state when the older pending request resolves? These problems can basically be solved by reducer not returning promises and handle that in the middle ware.

like image 101
HMR Avatar answered Oct 18 '22 03:10

HMR


You need to await the call to your async function in the reducer.

Right now, you call it, but never wait for it to finish.

like image 29
SLaks Avatar answered Oct 18 '22 02:10

SLaks


You need to use async/await in your reducer function too, otherwise getLocalStream() will just return a promise instance.

This is because, according to the docs,

When an async function is called, it returns a Promise. When the async function returns a value, the Promise will be resolved with the returned value. When the async function throws an exception or some value, the Promise will be rejected with the thrown value.

An async function can contain an await expression, that pauses the execution of the async function and waits for the passed Promise's resolution, and then resumes the async function's execution and returns the resolved value.

const reducer = async (state = {}, action) => { //HERE
  switch (action.type) {
    case 'SEND_CALL': {
      const { id } = action;
      const localStream =  await getLocalStream(); //HERE
      console.log('reducer localStream', localStream);
      const call = state.peer.call(id, localStream);
      return { ...state, call };
    }
    default: 
      return state;
  }
};
like image 1
Ayush Gupta Avatar answered Oct 18 '22 02:10

Ayush Gupta