1

I am building a todo-list like feature which adds a task when Enter is pressed on an input task field. The Enter calls an API (add Task) which takes approx 200ms to execute. Since this is blocking call it hinders my code to execute fully and affects the usability of my system. Here is a code example of what I am trying to achieve.

handleChange (event) {

          if (e.key === 'Enter') {

            targetTaskId = e.target.getAttribute("data-downlink")   
            this.props.addTask(this.props.currentProject.id, '', '', taskId, this.props.currentTasks) //this function calls an add Task API which halts my system momentarily

            targetSelector  = targetTaskId
            $('#' + targetSelector).focus()
            this.setState({activeTask: targetSelector})
            highlightActiveComponent(targetTaskId)

   } 

}

//addTask

   export function addTask (project_id, taskName, taskNotes, upLink, taskList) {
      console.log('Add Task API call', project_id, taskName, taskNotes, upLink)
      return (dispatch) => {
        callApi('tasks?projectId=' + project_id + '&name=' + taskName + '&notes=' + taskNotes + '&upLink=' + upLink, 'post')
          .then(res => {
            console.log('Response new task ', res)
            let newTask = {name: res.name, id: res.id, notes: res.notes, upLink: upLink, projectId: project_id, assignee: 0, completed: 0, tags: [], isLiked: false, stories: [], likes: [], downLink: res.downLink}
            let newTaskList = addTaskToTaskList(taskList, upLink, newTask)
            dispatch(updateTasks({currentTasks: newTaskList}))
            dispatch({ type: 'SET_ACTIVE_TASK_ID', payload: res.id })

          })
      }
   }

//Fetch

export const API_URL = 'https://clients.rohan.axcelmedia.ca/v1'

export default function callApi (endpoint, method = 'get', body) {
  let headers = {
    'Accept': 'application/json',
    'Content-Type' : 'application/json'
  }

  if (auth.loggedIn()) {
    headers = _.merge(headers, {
      Authorization: `Bearer ${auth.getToken()}`
    })
  }
  return fetch(`${API_URL}/${endpoint}`, {
    headers,
    method,
    body: JSON.stringify(body)
  }).then(response => {

    return response
  }).then(response => response.json().then(json => ({ json, response })))
    .then(({ json, response }) => {
      if (!response.ok) {
        return Promise.reject(json)
      }
      return json
    })
    .then(
      response => response,
      error => error
    )
}

Add Task to tasklist

    export function addTaskToTaskList(tasks, upLink, newTask){
        updateTaskDownLink(tasks, newTask.upLink, newTask.id)
        updateTaskUpLink(tasks, newTask.downLink, newTask.id)
        if(upLink == 0){
            tasks.unshift(newTask)
            // console.log("Added in the start", tasks)
            return JSON.parse(JSON.stringify(tasks))
        }
        let myIndex = getIndexOfTaskById(tasks, upLink)
        console.log("Added the new task from helper", myIndex)
        if (myIndex) {
          console.log("Added the new task")
          tasks.splice(myIndex + 1, 0, newTask);
          // console.log("New Task List", JSON.parse(JSON.stringify(tasks)))
        }

        return JSON.parse(JSON.stringify(tasks))
    }

  export function updateTaskUpLink(tasks, taskId, upLink){
      tasks.forEach(function(element, index) {
        if(element.id == taskId) { element.upLink = upLink }
      });

      return tasks
    }

    export function updateTaskDownLink(tasks, taskId, downLink){
        tasks.forEach(function(element, index) {
            if(element.id == taskId) { element.downLink = downLink }
        });

        return tasks
    }

My question is, is there anyway to call this API in a non-blocking fashion so that my code continues to execute and when the response from the api is received my cursor moves to the new task in a seamless manner. Any help would be appreciated. Thankyou [EDIT] : Added fetch function to demonstrate the async calls

Rafay Shahid
  • 47
  • 10
  • 1
    Make `addTask` make its HTTP requests asynchronously and use promises/callbacks most likely. – CollinD Aug 15 '18 at 21:57
  • 1
    javascript is asynchronous and non-blocking by design, perhaps there is something taking place elsewhere in your code that is causing your app to render unfavorably? Perhaps there is an expensive computation taking place in `addTask` or `highlightActiveComponent` – James Aug 15 '18 at 22:04
  • @james Yes the expensive computation is happening in add task which calls the api that takes some time to process. i am using isomorphic fetch and people have suggested me to use axios instead – Rafay Shahid Aug 16 '18 at 00:19
  • @RafayShahid it doesn't matter if you use fetch or axios or xhrhttp or whatever, all api calls in JS are asynchronous by default. There are only a hand full of synchronous operations in js and most of them have to do with file system operations. – James Aug 16 '18 at 02:40
  • @James ok so there is no way make this call non-blocking in js? – Rafay Shahid Aug 16 '18 at 10:13
  • it is already non-blocking - the lag you're experiencing is due to something else in your code, maybe a long for / while loop? maybe lets have a look at `updateTasks` ? or `addTaskToTaskList` ? do you do any super long iterations or deep comparisons in either of those? – James Aug 16 '18 at 14:32
  • @James yes you are right. there is a large forEach computation taking place in 2 functions updateTaskupLink/updateTaskDownlink. I have edited the page to demonstrate this. is there anyway to make this faster ? – Rafay Shahid Aug 17 '18 at 12:02
  • I edited my original answer below. To solve your actual problem, I think you need to change the data structure being used to store `tasks` in the first place. – James Aug 17 '18 at 20:48

2 Answers2

2

You should use something like Fetch API for call the API in a non-blocking way:

fetch("/api/v1/endpoint/5/", {
    method: "get",
    credentials: "same-origin",
    headers: {
        "Accept": "application/json",
        "Content-Type": "application/json"
    }
}).then(function(response) {
    return response.json();
}).then(function(data) {
    console.log("Data is ok", data);
}).catch(function(ex) {
    console.log("parsing failed", ex);
});

console.log("Ciao!");

The code that shows data in the snippet will be executed only when some data is returned by the server.

This means that in my example the log "Ciao!" will be showed before "Data is ok: ..."

Hope this helps :)

credits for the snippet: https://gist.github.com/marteinn/3785ff3c1a3745ae955c

lorenzo
  • 627
  • 8
  • 12
  • @Odle098 this is almost exactly what i am doing. in my utility there is a fetch function which returns the reponse in a promise based manner. To my knowledge isomorphic-fetch should not create this issue but i am confused right now – Rafay Shahid Aug 15 '18 at 22:22
  • @ZeyadEtman i am using isomorphic-fetch which is also promise based and works similarly as axios – Rafay Shahid Aug 15 '18 at 22:32
  • @RafayShahid maybe you should pass a function to your "addTask" method, then call that function inside the fetch function. In this way your new "addTask" should not return something – lorenzo Aug 15 '18 at 22:39
0

First of all return JSON.parse(JSON.stringify(tasks)) is redundant, you can just return tasks right there, that will probably fix your speed problem alone. But incase it doesn't.

Your code might be blocking due to this type of thing here

tasks.forEach(function(element, index) {
  if(element.id == taskId) { element.upLink = upLink }
});

return tasks

You iterate over the tasks array for updateTaskDownLink, again for updateTaskUpLink and probably again for getIndexOfTaskById, this is a lot of needless iteration.

Instead of searching through an array of tasks over and over, you should structure your tasks in a map

tasks = {
  "someTaskId": {
    id: "someTaskId",
    upLink: "uplink stuff",
    downLink: "downlink stuff"
  }
}

This way when you go to update the task its really simple and really fast

tasks[taskId].upLink = upLink or tasks[taskId].downLink = downLink

No iterating, no blocking, no problem.

Also, this data structure will make getIndexOfTaskById obsolete! because you already have the key needed to access that task! Hooray!

If you're wondering how to iterate over your tasks structured as a map like that see here

James
  • 4,927
  • 3
  • 22
  • 27
  • 1
    You have actually helped me solve a huge problem! I was told that the forEach speed may not be the factor for slow speeds and I ignored this fact completely but removing these 2 redundant forEach makes my Enter respond instantly. Thankyou very much!! :) – Rafay Shahid Aug 28 '18 at 04:57