Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular2 change service method from callback to Async

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

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

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

I'm having a hard time rewriting it to return a promise, since the result will always be within 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 do get a result back, but the component never sees it since it comes back before the results are returned.

I've tried doing an await on the query call, within the ConnectionPool function, but I get an error stating that await can only be called within an async function, even though I have async set on that method. The mssql package has an Async/ Await section, but the given syntax on that page gives errors, when I try it.

Any idea how I can write this using a promise?

like image 616
Ben Avatar asked Oct 31 '25 22:10

Ben


1 Answers

As you pointed out, there are 3 way to handle async functions: using callback, using promise, and using Async/ Await. I will try to show all three ways but you should learn about event loop in javascript and how it takes care of async functions.

Callback

Callback is technically fastest way to handle async functions but it is quite confusing at first and might create something called callback hell if not used properly. Callback hell is very terrible that someone even created a website for it http://callbackhell.com/.

So you 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

Promise is a special object that allows you to control async function and avoid callback hell because you won't have to use nested callback but only use one level then and catch function. 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);
                  });

            });
        })

    }
}

Async/await

Async/await was introduced to address the confusion you was having when dealing with callbacks and promises. Read more about 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";
    });
  };
}

Service would be exactly the same as in Promise because Async/await was created especially to deal with them.

NOTE: I remove some Typescript feature from your code because I am more accustomed to vanilla JS but you should be able to compile them because Typescript is a superset of JS.

like image 97
Nghia Tran Avatar answered Nov 03 '25 12:11

Nghia Tran