JavaScript technologies

AJAX and asynchronity

Horváth, Győző
senior lecturer
horvath.gyozo@inf.elte.hu

Financed from the financial support ELTE won from the Higher Education Restructuring Fund of the Hungarian Government

AJAX and asynchronity

References

Naive abstraction

function ajax(opts) { 
    var method    = opts.method || 'GET',
        url       = opts.url || '',
        getdata   = opts.getdata || '',
        postdata  = opts.postdata || '',
        success   = opts.success || function(){},
        error     = opts.error || function(){};

    method = method.toUpperCase();
    url = url+'?'+getdata;
    var xhr = new XMLHttpRequest();
    xhr.open(method, url, true);
    if (method === 'POST') {
        xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
    }
    xhr.setRequestHeader('HTTP_X_REQUESTED_WITH', 'XMLHttpRequest');
    xhr.onreadystatechange = function () {
        if (xhr.readyState == 4) {
            if (xhr.status == 200) {
                success(xhr, xhr.responseText);
            } else {
                error(xhr);
            }
        }
    };
    xhr.send(method == 'POST' ? postdata : null);
    return xhr;
}

Usage

$('#btnGet').on('click', function (e) {
    ajax({
        url: 'data1.json',
        method: 'get',
        success: function (data) {
            data = JSON.parse(data);
            console.log(data);
            ajax({
                url: 'data2.json',
                method: 'get',
                success: function (data) {
                    data = JSON.parse(data);
                    console.log(data);
                }
            });
        }
    });
});

jQuery: $.ajax()

$('#btnGet').on('click', function (e) {
    $.ajax({
        url: 'data1.json',
        dataType: 'json',
        type: 'get',
        success: function (data) {
            console.log(data);
            var ref = data.ref;
            $.ajax({
                url: 'data2.json',
                dataType: 'json',
                type: 'get',
                success: function (data) {
                    console.log(data);
                }
            });
        }
    });
});

The callback hell

Or: Pyramid of Doom

$('#btnGet').on('click', function (e) {
    $.ajax({
        success: function (data) {
            $.ajax({
                success: function (data) {
                    console.log(data);
                }
            });
        }
    });
});

Managing asynchronity

  • Events
  • Callbacks
  • Promises
  • Generator functions and promises

Callback: extracting named functions

$('#btnGet').on('click', onClick);

function onClick () {
    $.getJSON('data1.json', processData1);
}

function processData1 (data) {
    console.log(data);
    var ref = data.ref;
    $.getJSON('data2.json', processData2);
}

function processData2 (data) {
    console.log(data);
}

Promise

  • Representing a result of an operation, which is unknown in the present
  • Success/error

jQuery's own implementation

$('#btnGet').on('click', onClick);

function onClick () {
    $.getJSON('data1.json')
        .then(processData1)
        .then(processData2);
}

function processData1 (data) {
    console.log(data);
    var ref = data.ref;
    return $.getJSON('data2.json');
}

function processData2 (data) {
    console.log(data);
}

Promise API

var promise = new Promise(
  function (resolve, reject) { // (A)
    ...
    if (...) {
      resolve(value); // success
    } else {
      reject(reason); // failure
    }
  });

// Do not use this
promise.then(
  function (value) { /* fulfillment */ },
  function (reason) { /* rejection */ }
);

// Rather this one
promise.then(
  function (value) { /* fulfillment */ }
);

promise.catch(
  function (reason) { /* rejection */ }
);

httpGet() example

Source

// Definition
function httpGet(url) {
  return new Promise(
    function (resolve, reject) {
      var request = new XMLHttpRequest();
      request.onreadystatechange = function () {
        if (this.status === 200) {
          // Success
          resolve(this.response);
        } else {
          // Something went wrong (404 etc.)
          reject(new Error(this.statusText));
        }
      }
      request.onerror = function () {
        reject(new Error(
          'XMLHttpRequest Error: '+this.statusText));
      };
      request.open('GET', url);
      request.send();  
    }
  );
}

// Usage
httpGet('http://example.com/file.txt')
  .then(
    function (value) {
      console.log('Contents: ' + value);
    },
    function (reason) {
      console.error('Something went wrong', reason);
    });

Promisifying the naive ajax() function

function ajax(opts) {
    return new Promise(
        function (resolve, reject) {
            var method    = opts.method     || 'GET',
                url       = opts.url        || '',
                getdata   = opts.getdata    || '',
                postdata  = opts.postdata   || '';

            method = method.toUpperCase();
            url = url+'?'+getdata;
            var xhr = new XMLHttpRequest();
            xhr.open(method, url, true);
            if (method === 'POST') {
                xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
            }
            xhr.setRequestHeader('HTTP_X_REQUESTED_WITH', 'XMLHttpRequest');
            xhr.onreadystatechange = function () {
                if (xhr.readyState == 4) {
                    if (xhr.status == 200) {
                        resolve(xhr, xhr.responseText);
                    } else {
                        reject(xhr);
                    }
                }
            };
            xhr.send(method == 'POST' ? postdata : null);
        }
    ) 
}

Promisifying $.ajax() 1.

function getJSON (url) {
    return new Promise(
        function (resolve, reject) {
            $.ajax({
                url: url,
                type: 'get',
                dataType: 'json',
                success: function (data) {
                    resolve(data);
                },
                error: function (xhr, status) {
                    reject(status);
                }
            })
        }
    );
}

Promisifying $.ajax() 2.

function getJSON (url) {
    return Promise.resolve($.getJSON(url));
}

$('#btnGet').on('click', onClick);

function onClick () {
    getJSON('data1.json')
        .then(processData1)
        .then(processData2);
}

function processData1 (data) {
    console.log(data);
    var ref = data.ref;
    return getJSON('data2.json');
}

function processData2 (data) {
    console.log(data);
}

Generator functions and promises

$('#btnGet').on('click', onClick);

function onClick () {
    co(function* () {
        try {
            var data1 = yield getJSON('data1.json')
            var data2 = yield processData1(data1);
            processData2(data2);
        }
        catch(msg) {
            console.log(msg);
        };    
    });
}

function processData1 (data) {
    console.log(data);
    var ref = data.ref;
    return getJSON('data2.json');
}

function processData2 (data) {
    console.log(data);
}

Backbone + AJAX + REST API

Backbone.Sync

An object responsible for synchronization

Defaults to REST API

Overridable

Documentation

create  → POST   /collection
read    → GET    /collection[/id]
update  → PUT    /collection/id
patch   → PATCH  /collection/id
delete  → DELETE /collection/id

Collection settings

Need to set the url field in the collection.

export default Backbone.Collection.extend({

    url: '/api/codesnippets',

    // ...
});

Model settings

Sometimes need to set the id attribute in the model.


export default Backbone.Model.extend({

    idAttribute: '_id',

    // ...
});

Task

Connect the code snippet client to the REST API!