javascriptpromisebackendless

How to make .then completely finish before moving onto the next?


So, I'm adding an establishment to the database, then adding a special to the database. An establishment can have 1 to Many specials, so I'm trying to create a relation under the 3rd .then(). The thing is, then 3rd .then() is executing before an establishment and special is added to the database. I need to add the establishment and special to the database first because I need to obtain their objectIds, var establishmentObjectId; and var specialObjectId;

How can I go about making sure the the establishment and special objectIds are retrieved before the relation is made?

    //Save the establishment objectId
    var establishmentObjectId;
    //Save the special objectID
    var specialObjectId;

    //Save establishment
    .then(() => {
        //Get the add form data
        var addName = document.getElementById('add_name');
        var addCountry = document.getElementById('add_country');
        var addEstablishmentType = document.getElementById('add_establishment_type');
        var addCuisineType = document.getElementById('add_cusine_type');

        //Create establishment from user's enteries
        var establishment = {
            Name: addName.value,
            Address: addAddress.value,
            Suburb: addSuburb.value,
            Country: addCountry.value,
            Cuisine_Type: addCuisineType.value,
            Establishment_Type: addEstablishmentType.value
        }

        //Save establishment to db
        Backendless.Data.of('Establishment').save( establishment )
        .then( function( savedObject ) {
            establishmentObjectId = savedObject.objectId;
            console.log( "new Establishment instance has been saved" );
          })
        .catch( function( error ) {
            console.log( "an error has occurred " + error.message );
          });
    })
    //Save special
    .then(() => {
        //Get the add form data
        var addCategory = document.getElementById('add_category');
        var addTypeOfSpecial = document.getElementById('add_type_of_special');
        var addDescription = document.getElementById('add_description');

        //Create special from user's enteries
        var special = {
            Category: addCategory.value,
            Type_Of_Special: addTypeOfSpecial.value,
            Description: addDescription.value
        }

        //Save special to db
        Backendless.Data.of('Special').save( special )
        .then( function( savedObject ) {
            specialObjectId = savedObject.objectId;
            console.log( "new Special instance has been saved" );
            })
        .catch( function( error ) {
            console.log( "an error has occurred " + error.message );
            });
    })
    //Add special to establishment/form relation
    .then(() => {

        //These are undefined even though they are declared above
        console.log(establishmentObjectId);
        console.log(specialObjectId);

        var parentObject = { objectId:establishmentObjectId };
        var childObject = { objectId:specialObjectId };
        var children = [ childObject ];

        Backendless.Data.of( "Establishment" ).addRelation( parentObject, "establishmentSpecials", children )
        .then( function( count ) {
          console.log( "relation has been set" );
        })
        .catch( function( error ) {
          console.log( "server reported an error - " + error.message );
        });

    })

Many thanks


Solution

  • Whenever you're in a Promise chain, 99% of the time, when you create a new Promise (with an API call, or with new Promise, etc), you should return it, or otherwise put it together with something else (like a Promise.all and return that). This will mean that possible rejections can be handled at a higher level, and will mean that the next .then in a chain will start only once the prior .then finishes.

    Change every

    Backendless.Data.of(
    

    to

    return Backendless.Data.of(
    

    Also, you probably don't want to be catching at every level, like you're doing currently - whenever you catch, you turn a rejected Promise into a resolved Promise, and subsequent .thens may be running on the assumption that everything in the prior .then finished successfully. With sequential asynchronous operations like these, if there's an error anywhere, you probably want to stop the whole process, rather than try to continue anyway (which could result in bugs).

    For example, if Backendless.Data.of('Establishment').save fails, then establishmentObjectId never gets assigned to, and trying to use it later in the final .then won't work. Same sort of thing with specialObjectId.

    For this code, there's also a better pattern to follow: instead of using two separate .thens, one to get the establishmentObjectId and one to get the specialObjectId, consider letting those operations run in parallel, and run the final .then after they're done via Promise.all. Something along the lines of:

    const getEstablishmentObjectId = () => {
      //Get the add form data
      // ...
      //Create establishment from user's enteries
      var establishment = {
        // ...
      }
      //Save establishment to db
      return Backendless.Data.of('Establishment').save(establishment)
        .then(savedObject => savedObject.objectId);
    };
    const getSpecialObjectId = () => {
        // ...
        return Backendless.Data.of('Special').save(special)
          .then(savedObject => savedObject.objectId);
    };
    
    Promise.all([
      getEstablishmentObjectId(),
      getSpecialObjectId(),
    ]).then(([establishmentObjectId, specialObjectId]) => {
        console.log(establishmentObjectId);
        console.log(specialObjectId);
        var parentObject = {
          objectId: establishmentObjectId
        };
        // etc
      })
      .catch((error) => {
        console.log('Error', error);
      });