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?
source to share
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.
source to share