Bimalmithran P.B
Bimalmithran P.B

Reputation: 101

How can I use Promises in Aurelia?

I have 2 files 1. api_service.ts

import { HttpClient } from 'aurelia-http-client';
import { autoinject } from "aurelia-framework";
import { ObserverLocator } from "aurelia-binding";

@autoinject()
export class ApiServices {
    private app_url = 'http://example.com/';
    private host = 'http://api.example.com:8080';

    constructor(private store: Store, private client: HttpClient, private observerLocator: ObserverLocator) { }

    GetInstallationId() {
        let url = this.host + "/1/api/iid";
        let iid;
        this.client.get(url)
            .catch((err) => { })
            .then(res => {
                let response = JSON.parse(res.response);
                iid = response.iid;
            });
        return iid;
    }
}

here I am trying to get some value from the server and in the HTTP response, I can see that I am getting it.

  1. board_services.ts

import { Store } from './localstorage_service';
import { ApiServices } from './api_service';
import { autoinject } from "aurelia-framework";
import { ObserverLocator } from "aurelia-binding";

@autoinject()
export class AppServices {
	private installation_ID: string;

	constructor(private store: Store, private apiServices: ApiServices, private observerLocator: ObserverLocator) { }

	getInstallationID() {
		const iid = this.store.getValue('installation_ID');
		console.log("iid = " + iid);
		if (iid && iid != undefined) {
			console.log('inside if iid');
			// this.installation_ID = this.apiServices.GetInstallationId();
			this.installation_ID = iid;
		} else {
			console.log('inside else iid');
			this.installation_ID = this.apiServices.GetInstallationId();
		}
		this.store.setValue('installation_ID', this.installation_ID);
		console.log("after if condition iid = " + iid);
		return this.installation_ID;
	}
}

here in this code I'm calling the function GetInstallationId() from api_services.ts but it is not waiting for the http request to complete. Is there a way to make the statement in the next statement execute after the GetInstallationId() is executed?

Upvotes: 1

Views: 3314

Answers (1)

shanonvl
shanonvl

Reputation: 629

The short answer: you need to support asynchronous behavior.

You may do this via (a) Promises and/or (b) async/await. The former is supported in all browsers except IE 11 and there are polyfills like this one if you need to support IE 11. The latter is currently not supported in IE at all and as it requires new JS keywords/syntax must be used with a transpiler like Babel.

This answer will use Promises as they have fewer dependencies than async/await. The snippets below have been updated to use Promises -- look for the comments like:

// REPLACED THIS LINE ... // WITH THIS LINE

and

// ADDED THIS LINE

which identify the updates.

  1. GetInstallationId is an asynchronous function and must return a promise.

    
    class ApiServices { 
        ... 
        GetInstallationId() {
            let url = this.host + "/1/api/iid";
            return new Promise(function(accept, reject) {                
                let iid;
                this.client.get(url)
                    .catch((err) => { })
                    .then(res => {
                        let response = JSON.parse(res.response);
                        // REPLACED THIS LINE
                        // iid = response.iid;
                        // WITH THIS LINE which accepts the promise
                        accept(response.iid);
                    })
                    // ADDED THIS LINE to catch any errors 
                    // e.g. when invalid response received
                    .catch(reject);        
            });
        }
    
    }
    
  2. Because getInstallationID calls GetInstallationId, it too must return a promise. This example defines a separate function, handler, which is then bound via .bind(this) to ensure that whenever the function is executed, this will always refer back to the correct ApiService object.

    
    class AppServices {
        ...
        getInstallationID() {
    
    
      function handler(accept, reject) {
        const iid = this.store.getValue('installation_ID');
        console.log("iid = " + iid);
        if (iid && iid != undefined) {
            console.log('inside if iid');
            // this.installation_ID = this.apiServices.GetInstallationId();
            this.installation_ID = iid;
        } else {
            console.log('inside else iid');
            // REPLACED THIS LINE
            // this.installation_ID = this.apiServices.GetInstallationId();
            // WITH THIS LINE
            this.apiServices.GetInstallationId().then(accept,reject);
        }
        this.store.setValue('installation_ID', this.installation_ID);
        console.log("after if condition iid = " + iid);
        // REPLACED THIS LINE
        // return this.installation_ID;
        // WITH THIS LINE
        accept(this.installation_ID); 
      };
    
      return new Promise(handler.bind(this));        
    }        
    ...
    
    }

    The above function is just one example which will work cross browser. Another solution which requires ES6 support and/or transpilation is to use arrow functions:

    
        //
        // Arrow function version
        //
    class AppServices {
        ...
        getInstallationID() {
          // NOTE the => which is shorthand for `(function (accept,reject) {...}).bind(this)`
          return new Promise((accept, reject) => {
            const iid = this.store.getValue('installation_ID');
            console.log("iid = " + iid);
            if (iid && iid != undefined) {
                console.log('inside if iid');
                // this.installation_ID = this.apiServices.GetInstallationId();
                this.installation_ID = iid;
            } else {
                console.log('inside else iid');
                // REPLACED THIS LINE
                // this.installation_ID = this.apiServices.GetInstallationId();
                // WITH THIS LINE
                this.apiServices.GetInstallationId().then(accept,reject);
            }
            this.store.setValue('installation_ID', this.installation_ID);
            console.log("after if condition iid = " + iid);
            // REPLACED THIS LINE
            // return this.installation_ID;
            // WITH THIS LINE
            accept(this.installation_ID); 
          });        
        }        
        ...
    }
    

With the above refactoring, you can now call getInstallationID() from e.g. another view model like this:

import {AppServices} from '...';
import {autoinject} from 'aurelia-framework';
@autoinject()
export class SomeViewModel {
    constructor(appServices) {
        this.appServices = appServices;
    }

    callGetInstallationId() {

        this.appServices.getInstallationID().then(function(id) {
            // the ID that is passed here is the one that is `accept`ed.
            // ie. if AppServices.getInstallationID calls accept('123'), then 
            // the '123' will be passed to this function as the first argument
        }, function(e) {
            // this function will be called if AppServices.getInstallationID
            // calls reject.  e will be the Error object, if any, that is passed
            // to the reject method.
        });

    }
}

Upvotes: 7

Related Questions