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?
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With