Callback function
Callback function là function được truyền làm tham số của một function khác. Bằng cách gọi (callback) function được chỉ định qua tham số bên trong function, bạn có thể kiểm soát hành vi của function hoặc nhận kết quả không đồng bộ.
Callback function không phải là cú pháp cấp ngôn ngữ, mà được gọi là callback function như một design pattern.
Cách sử dụng callback function
Kiểm soát hành vi của function
Callback function có thể được sử dụng khi bạn muốn kiểm soát một phần hành vi của function từ bên ngoài.
greetNewUser là function chào mừng khách hàng mới.
Bằng cách truyền function hello và goodMorning làm callback function, bạn có thể kiểm soát cách chào mừng.
tsfunctiongreetNewUser (func : (name : string) => string) {console .log (func ("NewUser"));}functionhello (name : string) {return `Hello! ${name }!!`;}functiongoodMorning (name : string) {return `Good morning! ${name }!!`;}// Hello! NewUser!!greetNewUser (hello );// Good morning! NewUser!!greetNewUser (goodMorning );
tsfunctiongreetNewUser (func : (name : string) => string) {console .log (func ("NewUser"));}functionhello (name : string) {return `Hello! ${name }!!`;}functiongoodMorning (name : string) {return `Good morning! ${name }!!`;}// Hello! NewUser!!greetNewUser (hello );// Good morning! NewUser!!greetNewUser (goodMorning );
Nhận kết quả không đồng bộ
Callback function cũng có thể được sử dụng khi bạn muốn nhận và xử lý kết quả của function không đồng bộ.
Ví dụ sau là sample code đọc file của fs module trong Node.js.
Việc đọc file được thực thi không đồng bộ, và sau khi đọc hoàn tất, callback function được gọi để truyền kết quả đọc không đồng bộ về phía gọi.
tsimportfs from "fs";fs .readFile ("./user.txt", "utf-8", (err ,data ) => {if (err ) {console .error (err );}console .log (data );});
tsimportfs from "fs";fs .readFile ("./user.txt", "utf-8", (err ,data ) => {if (err ) {console .error (err );}console .log (data );});
Callback hell
Sample code sau thực hiện đọc file theo trình tự:
- Đọc file A
- Đọc file B được ghi trong file A
- Đọc file C được ghi trong file B
Khi gọi callback function bên trong callback function như thế này, việc lồng nhau (nesting) trở nên sâu và code khó đọc hơn, vấn đề này được gọi là callback hell.
tsimportfs from "fs";fs .readFile ("./a.txt", "utf-8", (err ,data ) => {fs .readFile (data , "utf-8", (err ,data ) => {fs .readFile (data , (err ,data ) => {console .log (data );});});});
tsimportfs from "fs";fs .readFile ("./a.txt", "utf-8", (err ,data ) => {fs .readFile (data , "utf-8", (err ,data ) => {fs .readFile (data , (err ,data ) => {console .log (data );});});});
Trong trường hợp như thế này, bạn có thể giải quyết bằng cách sử dụng Promise thay vì callback function.
Sau khi Promise ra đời để giải quyết vấn đề callback hell, việc sử dụng Promise thay vì callback function để lấy kết quả xử lý không đồng bộ đã trở nên phổ biến.
tsimport {promises asfs } from "fs";fs .readFile ("a.txt", "utf-8").then ((data ) =>fs .readFile (data , "utf-8")).then ((data ) =>fs .readFile (data , "utf-8")).then ((data ) =>console .log (data ));
tsimport {promises asfs } from "fs";fs .readFile ("a.txt", "utf-8").then ((data ) =>fs .readFile (data , "utf-8")).then ((data ) =>fs .readFile (data , "utf-8")).then ((data ) =>console .log (data ));
Định nghĩa kiểu cho callback function
Kiểu của callback function được viết dưới dạng (arg: [kiểu của tham số]) => [kiểu giá trị trả về].
Callback function chỉ là một function, nên đây chỉ là khai báo kiểu function làm kiểu của tham số.
📄️ Khai báo kiểu function
Trong TypeScript, bạn có thể khai báo kiểu của function. Khai báo kiểu function là việc định nghĩa interface của function mà không chỉ ra implementation.
tsfunctiongreetNewUser (func : (name : string) => string) {console .log (func ("NewUser"));}
tsfunctiongreetNewUser (func : (name : string) => string) {console .log (func ("NewUser"));}
Callback đồng bộ và không đồng bộ
Như đã thấy trong các ví dụ cách sử dụng, callback function có loại đồng bộ và không đồng bộ.
Callback function đồng bộ
Callback function đồng bộ là callback được gọi ngay lập tức một cách đồng bộ.
Ví dụ điển hình là tham số của Array.map trong standard API nhận callback function đồng bộ.
tsconstnumbers = [1, 2, 3];constdoubles =numbers .map ((n : number) => {returnn * 2;});// 2, 4, 6console .log (doubles );
tsconstnumbers = [1, 2, 3];constdoubles =numbers .map ((n : number) => {returnn * 2;});// 2, 4, 6console .log (doubles );
Callback function không đồng bộ
Callback function không đồng bộ là callback được gọi không đồng bộ như API request.
Ví dụ điển hình là tham số của setTimeout nhận callback function không đồng bộ.
Trong ví dụ sau, callback function được truyền cho setTimeout được gọi không đồng bộ sau 1 giây,
và kết quả hiển thị trên console theo thứ tự hello, This is callback function!.
tssetTimeout (() => {console .log ("This is callback function!");}, 1000);console .log ("hello");// hello// This is callback function!
tssetTimeout (() => {console .log ("This is callback function!");}, 1000);console .log ("hello");// hello// This is callback function!
Callback function đồng bộ và xử lý không đồng bộ
Điều gì xảy ra khi truyền async function trả về Promise cho callback function đồng bộ như Array.map?
doublePromise là async function thực thi xử lý nhân đôi giá trị được truyền một cách không đồng bộ và trả về giá trị.
Lúc này, vì doublePromise là async function nên không trả về giá trị đã nhân đôi mà trả về Promise, do đó doubles trở thành mảng các Promise.
tsfunctiondoublePromise (n : number):Promise <number> {return newPromise ((resolve ) => {setTimeout (() => {resolve (n * 2);}, 100);});}constnumbers = [1, 2, 3];constdoubles =numbers .map (doublePromise );// [Promise: {}, Promise: {}, Promise: {}]console .log (doubles );
tsfunctiondoublePromise (n : number):Promise <number> {return newPromise ((resolve ) => {setTimeout (() => {resolve (n * 2);}, 100);});}constnumbers = [1, 2, 3];constdoubles =numbers .map (doublePromise );// [Promise: {}, Promise: {}, Promise: {}]console .log (doubles );
Khi truyền async function cho callback function đồng bộ, bạn cần resolve kết quả Promise.
tsfunctiondoublePromise (n : number):Promise <number> {return newPromise ((resolve ) => {setTimeout (() => {resolve (n * 2);}, 100);});}(async function () {constnumbers = [1, 2, 3];constdoubles = awaitPromise .all (numbers .map (doublePromise ));// [2, 4, 6]console .log (doubles );})();
tsfunctiondoublePromise (n : number):Promise <number> {return newPromise ((resolve ) => {setTimeout (() => {resolve (n * 2);}, 100);});}(async function () {constnumbers = [1, 2, 3];constdoubles = awaitPromise .all (numbers .map (doublePromise ));// [2, 4, 6]console .log (doubles );})();
Array.map được định nghĩa kiểu để chấp nhận cả async function làm callback function, nên không xảy ra lỗi kiểu.
Nếu định nghĩa kiểu callback function chỉ chấp nhận synchronous function, sẽ xảy ra lỗi kiểu khi truyền async function.
tstypeUser = {name : string;};functiongreetUser (getUser : () =>User ) {constuser =getUser ();console .log (`Hello, ${user .name }`);}functionfetchUserFromDB ():Promise <User > {return newPromise <User >((resolve ) => {setTimeout (() => {resolve ({name : "Tuan" });}, 1000);});}Argument of type '() => Promise<User>' is not assignable to parameter of type '() => User'. Property 'name' is missing in type 'Promise<User>' but required in type 'User'.2345Argument of type '() => Promise<User>' is not assignable to parameter of type '() => User'. Property 'name' is missing in type 'Promise<User>' but required in type 'User'.greetUser (); fetchUserFromDB
tstypeUser = {name : string;};functiongreetUser (getUser : () =>User ) {constuser =getUser ();console .log (`Hello, ${user .name }`);}functionfetchUserFromDB ():Promise <User > {return newPromise <User >((resolve ) => {setTimeout (() => {resolve ({name : "Tuan" });}, 1000);});}Argument of type '() => Promise<User>' is not assignable to parameter of type '() => User'. Property 'name' is missing in type 'Promise<User>' but required in type 'User'.2345Argument of type '() => Promise<User>' is not assignable to parameter of type '() => User'. Property 'name' is missing in type 'Promise<User>' but required in type 'User'.greetUser (); fetchUserFromDB