Angular2 change service method from callback to Async

I started a simple Angular2 Electron app and I have a service method that queries my local SQL Server database. Everything works fine so far. Now I'm trying to get the results of a service DB call to my component and display it somehow.

The problem is that the request logic is more written for the callback syntax:

sql.query(sqlString, (err, result) => {
    ...
    callback(result);
    ...
});

      

I find it difficult to rewrite it to return a promise, since the result will always be within the result parameter of the query command function. My component looks like this:

export class LinkDocRetriever {

  constructor(private myService: MyService) {  }

  results = "";

  loadMyData(id: number): void {

    let tcData = this.myService.getMyData();
    tcData.forEach(element => {
      this.results += element.FileName + " " +  "....\n";
    });

  };
}

      

And my service looks like this:

import { Injectable } from "@angular/core";
import * as sql from "mssql";

@Injectable()
export class MyService {

    getMyData():Array<MyItem> {

        let myData:Array<MyItem> = [];

        let config = {
            user: "sa",
            password: "xxx",
            server: "localhost",
            database: "mydb"
        };

        const pool1 = new sql.ConnectionPool(config, err => {

            if (err) {
                console.log("connect erro: " + err);
            }

            let q:string = `SELECT TOP 10 * FROM MyTable;`;

            let final = pool1.request()
            .query<MyItem>(q, (err, result) => {
                if (err) {
                    console.log("request err: " + err);
                }

                console.log("db result count: " + result.recordsets[0].length);
                result.recordsets[0].forEach(row => {
                    myData.push(row);
                });
            });
        });
        return myData;
    }
}

      

I get the result back, but the component never sees it as it returns before returning the results.

I tried to make the wait in the request call in the ConnectionPool function, but I get an error that the wait can only be called in the async function, even though I have an asynchronous set of this method. The mssql package has an Async / Await section , but the given syntax on this page gives errors when I try it.

Any idea how I can write this using a promise?

+3


source to share


1 answer


As you pointed out, there are 3 ways to handle asynchronous functions: using callbacks, using promises, and using Async / Await. I'll try to show all three ways, but you should learn about the event loop in javascript and how it takes care of async functions.

Callback

Callback is the fastest way to handle asynchronous functions, but at first glance it is quite confusing and can create something called callback hell if not used properly. Callback attorney is very scary that someone even set up a website for him http://callbackhell.com/ .

So, the code can be rewritten as:

export class LinkDocRetriever {

  constructor(private myService: MyService) {  }

  results = "";

  loadMyData(id: number): void {

    // call getMyData with a function as argument. Typically, the function takes error as the first argument 
    this.myService.getMyData(function (error, tcData) {
       if (error) {
         // Do something
       }

       tcData.forEach(element => {
         this.results += element.FileName + " " +  "....\n";
       });
    });
  };
}

      

Service

import { Injectable } from "@angular/core";
import * as sql from "mssql";

@Injectable()
export class MyService {
    // Now getMyData takes a callback as an argument and returns nothing
    getMyData(cb) {

        let myData = [];

        let config = {
            user: "sa",
            password: "xxx",
            server: "localhost",
            database: "mydb"
        };

        const pool1 = new sql.ConnectionPool(function(config, err) {

            if (err) {
                // Error occured, evoke callback
                return cb(error);
            }

            let q:string = `SELECT TOP 10 * FROM MyTable;`;

            let final = pool1.request()
            .query<MyItem>(q, (err, result) => {
                if (err) {
                    console.log("request err: " + err);
                    // Error occured, evoke callback
                    return cb(error);
                }

                console.log("db result count: " + result.recordsets[0].length);
                result.recordsets[0].forEach(row => {
                    myData.push(row);
                });

                // Call the callback, no error occured no undefined comes first, then myData
                cb(undefined, myData);
            });

        });
    }
}

      

Promise

A Promise is a special object that allows you to manipulate an async function and avoid a callback because you don't need to use a nested callback, but only use one function then

and catch

. Read more about Promise here

component



export class LinkDocRetriever {

  constructor(private myService: MyService) {  }

  results = "";

  loadMyData(id: number): void {
    this.myService.getMyData()
      .then((tcData) => {
         // Promise uses then function to control flow
         tcData.forEach((element) => {
           this.results += element.FileName + " " +  "....\n";
         });
      })
      .catch((error) => {
         // Handle error here
      });

  };
}

      

Service

@Injectable()
export class MyService {
    // Now getMyData doesn't take any argument at all and return a Promise
    getMyData() {

        let myData = [];

        let config = {
            user: "sa",
            password: "xxx",
            server: "localhost",
            database: "mydb"
        };

        // This is what getMyData returns
        return new Promise(function (resolve, reject) {
            const pool1 = new sql.ConnectionPool((config, err) => {

                if (err) {
                    // If error occurs, reject Promise
                    reject(err)
                }

                let q = `SELECT TOP 10 * FROM MyTable;`;

                let final = pool1.request()
                  .query(q, (err, result) => {
                      if (err) {
                          // If error occurs, reject Promise
                          reject(err)
                      }

                      console.log("db result count: " + result.recordsets[0].length);
                      result.recordsets[0].forEach((row) => {
                          myData.push(row);
                      });

                      // 
                      resolve(myData);
                  });

            });
        })

    }
}

      

Asynchronous / Await

Async / await was introduced to clear up the confusion you encountered with callbacks and promises. More on async / await here

component



export class LinkDocRetriever {

  constructor(private myService: MyService) {  }

  results = "";

  // Look. loadMyData now has to have async keyword before to use await. Beware, now loadMyData will return a Promise.
  async loadMyData(id) {

    // By using await, syntax will look very familiar now
    let tcData = await this.myService.getMyData(tcData);
    tcData.forEach((element) => {
      this.results += element.FileName + " " +  "....\n";
    });
  };
}

      

The service will be exactly the same as in Promise , because Async / await was created specifically to eliminate them.

NOTE. I am removing some Typescript function from your code because I am more used to vanilla JS, but you have to compile them because Typescript is a superset of JS.

+3


source







All Articles