Search code examples
javascriptreactjsasync-awaitcustom-events

Custom status change events in Javascript


I have an asynchronous function that performs various await tasks. I am trying to inform my UI in React when the status of the function changes or when one of the tasks is completed.

const foo = async () => {
    // trigger on load event

    await task1();
    // trigger task1 done event

    await task2();
    // trigger task2 done event

    await task3();
    // trigger on done event
}

I also want to be able to specify callbacks for each event, like so:

const bar = foo();
foo.on_load(() => {
    // some code goes here
});
foo.on_done(() => {
    // some code goes here
});

Another alternative would be something like this:

const bar = foo();
foo.on('status_change', status => {
    // read the status here and do something depending on the status
})

I have been reading about custom events in JS but not sure how to use them for this. Or maybe there's another way to do this in React.

Any ideas would be helpful. Thanks!

EDIT

var uploadTask = storageRef.child('images/rivers.jpg').put(file);

// Register three observers:
// 1. 'state_changed' observer, called any time the state changes
// 2. Error observer, called on failure
// 3. Completion observer, called on successful completion
uploadTask.on('state_changed', function(snapshot){
  // Observe state change events such as progress, pause, and resume
  // Get task progress, including the number of bytes uploaded and the total number of bytes to be uploaded
  var progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
  console.log('Upload is ' + progress + '% done');
  switch (snapshot.state) {
    case firebase.storage.TaskState.PAUSED: // or 'paused'
      console.log('Upload is paused');
      break;
    case firebase.storage.TaskState.RUNNING: // or 'running'
      console.log('Upload is running');
      break;
  }
}, function(error) {
  // Handle unsuccessful uploads
}, function() {
  // Handle successful uploads on complete
  // For instance, get the download URL: https://firebasestorage.googleapis.com/...
  uploadTask.snapshot.ref.getDownloadURL().then(function(downloadURL) {
    console.log('File available at', downloadURL);
  });
});

I was trying to achieve something like the above code, taken from the firebase documentation on uploading files

This is where I've gotten so far:

class Task {
  constructor() {
    this.first = null;
    this.second = null;
  }

  on(keyword, callback) {
    switch (keyword) {
      case "first":
        this.first = callback;
        break;
      case "second":
        this.second = callback;
        break;
      default:
        // throw new error
        break;
    }
  }
}

const timeout = async time => {
  return new Promise(resolve => setTimeout(resolve, time));
};

const foo = () => {
  const task = new Task();

  timeout(2000).then(async () => {
    task.first && task.first();
    await timeout(2000);
    task.second && task.second();
  });

  console.log("returning");
  return task;
};

const taskObject = foo();
taskObject.on("first", () => console.log("executing first callback"));
taskObject.on("second", () => console.log("executing second callback"));

Is there a better way to do this - without having the nested thens? Which approach would be better and when? EDIT - removed nested then clauses and replaced with then and await

PS: for my requirements, having callbacks would be sufficient. This is just so I can understand the concept better. Thanks!


Solution

  • I'm going to assume there's a reason for you not simply calling some named method after each async step has complete, i.e., you want to be able to plug in different handlers for each event. Here is one way to go about it - whether or not it's the best is hard to tell from the little context provided:

    const foo = async (handlers) => {
      handlers.onLoad && handlers.onLoad();
    
      await task1();
      handlers.onTask1Complete && handlers.onTask1Complete();
    
      await task2();
      handlers.onTask2Complete && handlers.onTask2Complete();
    }
    const myHandlers = {
      onLoad: () => {
        // do stuff
      },
      onTask1Complete: () => {
        // do other stuff
      },
      onTask2Complete: () => {
        // etc
      }
    };
    
    foo(myHandlers);
    

    Note that it lets you specify only the handlers you need. A more flexible approach would be to a publish-subscribe model, where a subscribe method pushes a function to an array of handlers, all of which are called when the event occurs.