0

How to ensure, in JavaScript (jquery) that some actions are performed one after other, in an order.

Say, I need to load schools collection BEFORE loading teachers, in order to assing the myTeacher.SchoolName = schools[myTeacher.SchoolId].name;

The pseudo code bellow:

const studentsUrl='api/students', teachersUrl='api/teachers', schoolsUrl='api/schools';
let students = null, teachers = null, schools = null;

$(document).ready(function () {
    getSchools(); 
    getTeachers(); 
    getStudents(); 
});

function getSchools() {
    $.get(schoolsUrl, function (data) {
            window.schools = data;
        });
}

function getTeachers() {
    $.get(teachersUrl, function (data) {
            window.teachers = data;

            // >>> SHOULD BE SURE, SCHOOLS already loaded!!!
            $.each(teachers, function (key, item) {
                item.school = schools[item.schoolId].name;
            });
        });
}

function getStudents() {
    $.get(studentsUrl, function (data) {
            window.students = data;

            // >>> SHOULD BE SURE, TEACEHRS already loaded!!!
            $.each(students, function (key, item) {
                item.teacher = teachers[item.teacherId].name;
            });
        });
}

PS.

Is there another way to assure order but the encapsulation of one function at the end of another?

serge
  • 13,940
  • 35
  • 121
  • 205

6 Answers6

1

Since you only need all the results available and each request does not depend on the previous you can use jQuery.when

let students = null;
let teachers = null;
let schools = null;

$(document).ready(function() {
  $.when(
      getSchools(),
      getTeachers()
  ).done(function(shoolResults, teacherResults) {
      window.schools = shoolResults;
      window.teachers = teacherResults;
      handleTeachers();
      getStudents();
  });


  function getSchools() {
    return $.ajax({
      type: 'GET',
      url: schoolsUrl
    });
  }

  function getTeachers() {
    return $.ajax({
      type: 'GET',
      url: teachersUrl
    });
  }

  function handleTeachers() {
   $.each(teachers, function (key, item) { 
     item.school = schools[item.schoolId].name;
    });
  }
});
Gabriele Petrioli
  • 191,379
  • 34
  • 261
  • 317
  • in fact, I need to obtain the schools, then the teachers(having school-id), and only then the students(having a teacher-id), but I see the principle... ) thanks! – serge Oct 12 '18 at 16:11
  • I am afraid the $when will not assure in that example that schoolResults will be loaded BEFORE teachers.... – serge Oct 12 '18 at 16:40
  • @Serge it will assure that both have loaded when the`.done` is called, and inside the `done` you have, as arguments, the results of each call. – Gabriele Petrioli Oct 12 '18 at 17:13
  • but I need to ensure schools loaded *before* teachers, here we don't have an exact order (schools=> teachers), but an assurance both are loaded (we don't know the order) – serge Oct 12 '18 at 17:22
1

As others already suggested you can chain requests. I made few changes to your code.

  1. Added Strict Mode it helps to prevent bugs
  2. The code wrapped in IFFE in order to prevent global pollution

If all apis belong to the same server you can process all this data on server side and return one filled json. in this way your server will do a little extra work on constructing this json but in other hand you will make only one ajax request instead of 3. This will work faster and you can cache this json for some time

Code for the first solution

(function () {

'use strict';
const studentsUrl = 'api/students';
const teachersUrl = 'api/teachers';
const schoolsUrl = 'api/schools';

let students = null;
let teachers = null;
let schools = null;

var scoolData = {
    schools: null,
    teachers: null,
    students: null
};

$(document).ready(function () {
    getSchools().then(function (schools) {
        scoolData.schools = schools;
        getTeachers().then(function (teachers) {
            scoolData.teachers = teachers;
            $.each(scoolData.teachers, function (key, item) {
                item.school = scoolData.schools[item.schoolId].name;
            });
        });
    });
});

function getSchools() {
    return $.get(schoolsUrl);
}

function getTeachers() {
    return $.get(teachersUrl,
        function (result) {
            scoolData.teachers = result;

            // >>> SHOULD BE SURE, SCHOOLS already loaded!!!
            $.each(teachers, function (key, item) {
                item.school = scoolData.schools[item.schoolId].name;
            });
        });
}
})();
serge
  • 13,940
  • 35
  • 121
  • 205
1

If you want them in order (though I'm not sure I understand why, since you retrieve all schools/teachers/students anyway), you can simply do this.

Note: get* functions are dummies in the following sample. Instead, just return the result of $.get calls from them:

function getSchools() {
  return Promise.resolve({1: {name: 'school1'}});
}

function getTeachers() {
  return Promise.resolve({1: {name: 'teacher1', schoolId: 1}});
}

function getStudents() {
  return Promise.resolve({1: {name: 'student1', teacherId: 1}});
}

(async () => {
  const schools = await getSchools();
  const teachers = await getTeachers();
  const students = await getStudents();

    // Alternative for the $.each code
  Object.values(teachers).forEach(teacher => teacher.school = schools[teacher.schoolId].name);
  Object.values(students).forEach(student => student.teacher = teachers[student.teacherId].name);
  
  console.log(schools, teachers, students);
})();

Another note: this is ES8 code, I'll post a non async/await version if you need to support older browsers and can't use a transpiler like Babel.

Non ES8-dependent code:

function getSchools() {
  return Promise.resolve({1: {name: 'school1'}});
}

function getTeachers() {
  return Promise.resolve({1: {name: 'teacher1', schoolId: 1}});
}

function getStudents() {
  return Promise.resolve({1: {name: 'student1', teacherId: 1}});
}

let schools = null, teachers = null, students = null;

getSchools().then(_schools => {
  schools = _schools;
  return getTeachers();
}).then(_teachers => {
  teachers = _teachers;
  return getStudents();
}).then(_students => {
  students = _students;

  for (var _ in teachers) {
    teachers[_].school = schools[teachers[_].schoolId].name;
  }
  for (var _ in students) {
    students[_].teacher = teachers[students[_].teacherId].name
  }

  console.log(schools, teachers, students);
});
Jeto
  • 14,596
  • 2
  • 32
  • 46
  • the get functions could take some params... but i supposed to fill-in "global" collections, then use them inside teachers and students functions – serge Oct 12 '18 at 16:54
  • also, as is, the get functions code does not return anything.. i don't know if await is ok with it – serge Oct 12 '18 at 16:55
  • @Serge Just return the result of `$.get` each time, and remove the `$.each` code from within the functions themselves. Put them after the three await's. – Jeto Oct 12 '18 at 16:56
  • @Serge I've added a working snippet with dummy `get*` functions. See my comments. – Jeto Oct 12 '18 at 16:59
  • I see the principle... perhaps is OK for Chrome... could you for the completion post an example that will work for the most of the browsers? – serge Oct 12 '18 at 17:06
  • is not really clear for me, how can I replace `Promise.Resolve` with get... should it be `return $.get(schoolsUrl)`... don't think so... – serge Oct 12 '18 at 17:10
  • @Serge Yes it is, just do that. `$.get` returns a `Promise` equivalent. – Jeto Oct 12 '18 at 17:12
  • yes, but does the $get return the data, for `const schools = await getSchools();` if `getSchools{ return $get(schoolsUrl); }` – serge Oct 12 '18 at 17:20
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/181774/discussion-between-jeto-and-serge). – Jeto Oct 12 '18 at 17:21
0

Call getTeachers(); when getSchools(); return success or complete, success preferred since complete runs if there's an error..

DoobyScooby
  • 367
  • 3
  • 6
  • 17
0

I think you are looking for this one.

 getSchools().done(function(data){
      var someId = data.findThatId;
      getTeachers(someId);
 }); 

You will need to return data from ajax call to get data in done.

Void Spirit
  • 879
  • 6
  • 18
0

You may load them asynchronously but you have to wait until both calls are finished.

To achieve this, add return before your ajax calls and combine the results in your ready function (not in the success handler of the teachers call):

let schoolsPromise = getSchools();
let teachersPromise = getTeachers();
$.when(schoolsPromise, teachersPromise)
  .then((schools, teachers) => {
    $.each(teachers, (key, item) => {
      item.school = schools[item.schoolId].name;
    });
  });
Hero Wanders
  • 3,237
  • 1
  • 10
  • 14
  • should I add `return` and keep `function(result)` with the $get call? Gabriele Petrioli removed it from the functions... – serge Oct 12 '18 at 16:21
  • similar to schools, students have the teacher-id, so I need to load first teachers... then students – serge Oct 12 '18 at 16:24
  • If you need to do something (which doesn't need the other calls' results) to an ajax call result, the success handler is okay. At least you have to store some of the objects but you may also do that in an additional `.then(...)` callback in the ready function. If course you can reuse the promise variables by adding another `$.when(studentsPromise, teachersPromise).then(...)` which combines the data as desired. – Hero Wanders Oct 12 '18 at 16:32