Nhảy tới nội dung

Tuple

Function của TypeScript chỉ có thể trả về 1 giá trị. Tuy nhiên, thực tế có lúc muốn trả về nhiều giá trị. Trong trường hợp đó, có thể đặt tất cả giá trị muốn trả về vào mảng và return.

Giá trị trả về của function sau là hằng số, nhưng hãy hiểu như kết quả tính toán thực tế.

ts
function tuple() {
//...
return [1, "ok", true];
}
ts
function tuple() {
//...
return [1, "ok", true];
}

Vấn đề của mảng

Trong ví dụ trên, kiểu nào là phù hợp cho giá trị trả về? Nếu bạn đã đọc từ trang mảng, có thể nghĩ đến any[] hoặc unknown[] vì đây là kiểu có thể chứa bất kỳ thứ gì.

ts
const list: unknown[] = tuple();
 
list[0].toString();
Object is of type 'unknown'.2571Object is of type 'unknown'.
ts
const list: unknown[] = tuple();
 
list[0].toString();
Object is of type 'unknown'.2571Object is of type 'unknown'.

Tuy nhiên, không thể gọi method từ list[n]. Vì mỗi phần tử của listunknown.

Vậy có nên dùng any[] làm kiểu giá trị trả về không? Điều đó cũng có vấn đề. Đang dùng TypeScript để tận hưởng lợi ích của kiểu, nhưng ở đây lại code như thể không có kiểu thì nhạt nhẽo. Đó là lúc có thể dùng tuple.

Kiểu của tuple

Kiểu của tuple đơn giản, chỉ cần viết [] và đặt kiểu bên trong. Nghĩa là, function tuple() ở trên có thể nói là có giá trị trả về như sau.

ts
const list: [number, string, boolean] = tuple();
ts
const list: [number, string, boolean] = tuple();

Tương tự có thể viết ở giá trị trả về của function.

ts
function tuple(): [number, string, boolean] {
//...
return [1, "ok", true];
}
ts
function tuple(): [number, string, boolean] {
//...
return [1, "ok", true];
}

Kiểu mảng có 2 cách viết T[]Array<T> nhưng tuple chỉ có cách viết này.

Đặt label cho tuple

Khi function trả về tuple toàn giá trị cùng kiểu, có thể khó hiểu kiểu đó biểu thị điều gì. Trong trường hợp đó, có thể đặt label cho tuple.

ts
const coordinate: [x: number, y: number, z: number] = tuple();
ts
const coordinate: [x: number, y: number, z: number] = tuple();

Với ví dụ này, dễ hiểu rằng đây là giá trị trả về của function trả về tọa độ 3 chiều.

Truy cập tuple

Biến nhận tuple có thể sử dụng trực tiếp property và method của kiểu bên trong.

ts
const list: [number, string, boolean] = tuple();
 
list[0].toExponential();
list[1].length;
list[2].valueOf();
ts
const list: [number, string, boolean] = tuple();
 
list[0].toExponential();
list[1].length;
list[2].valueOf();

Biến nhận tuple không thể truy cập phần tử ngoài phạm vi đã định nghĩa trong tuple.

ts
const list: [number, string, boolean] = tuple();
 
list[5];
Tuple type '[number, string, boolean]' of length '3' has no element at index '5'.2493Tuple type '[number, string, boolean]' of length '3' has no element at index '5'.
ts
const list: [number, string, boolean] = tuple();
 
list[5];
Tuple type '[number, string, boolean]' of length '3' has no element at index '5'.2493Tuple type '[number, string, boolean]' of length '3' has no element at index '5'.

Do đó, ngay cả khi thực hiện thao tác tăng phần tử mảng như list.push(), cũng không thể sử dụng phần tử đó.

Truy cập tuple có label

Label chỉ được dùng để cải thiện khả năng đọc, không thể dùng label để truy cập trong code thực tế.

ts
const coordinate: [x: number, y: number, z: number] = tuple();
 
coordinate[0];
coordinate.x;
Property 'x' does not exist on type '[x: number, y: number, z: number]'.2339Property 'x' does not exist on type '[x: number, y: number, z: number]'.
ts
const coordinate: [x: number, y: number, z: number] = tuple();
 
coordinate[0];
coordinate.x;
Property 'x' does not exist on type '[x: number, y: number, z: number]'.2339Property 'x' does not exist on type '[x: number, y: number, z: number]'.

Truy cập tuple bằng destructuring assignment

Giá trị trả về của function tuple() có thể nhận bằng destructuring assignment như sau.

ts
const [num, str, bool]: [number, string, boolean] = tuple();
ts
const [num, str, bool]: [number, string, boolean] = tuple();

Ngoài ra, nếu chỉ cần một số giá trị trả về nhất định, chỉ viết , mà không viết tên biến.

ts
const [, , bool]: [number, string, boolean] = tuple();
ts
const [, , bool]: [number, string, boolean] = tuple();

Trường hợp sử dụng tuple

Khi lập trình bất đồng bộ trong TypeScript, có lúc muốn thực hiện các xử lý tốn thời gian song song thay vì tuần tự. Lúc đó TypeScript sử dụng Promise.all(). Đây là lúc tuple hữu ích.
Giải thích chi tiết về Promise có trang chuyên đề trong sách này. Ở đây chỉ cần nhớ rằng biến kiểu Promise<T> khi đặt await phía trước sẽ lấy ra được T. Ngoài ra, T này được gọi là generics, cũng có trang chuyên đề.

📄️ Xử lý bất đồng bộ

Nếu bạn muốn xây dựng một ứng dụng JavaScript nghiêm túc, bạn sẽ không thể tách rời khỏi xử lý bất đồng bộ. Ban đầu có thể khó hiểu, nhưng giờ đây JavaScript đã có các tính năng giúp thao tác với xử lý bất đồng bộ một cách trực quan hơn nhiều, làm giảm đáng kể rào cản.

📄️ Generics

Việc kết hợp giữa type safety và code reusability là một thách thức. Nếu cố gắng sử dụng cùng một đoạn code cho nhiều kiểu dữ liệu khác nhau, type safety sẽ bị hy sinh. Ngược lại, nếu tập trung vào type safety, bạn sẽ phải viết nhiều đoạn code tương tự nhau, khiến code reusability khó đạt được. Generics là tính năng được giới thiệu để giải quyết vấn đề này. Với generics, bạn có thể đồng thời đảm bảo type safety và code reusability.

ts
const promise: Promise<number> = yyAsync();
const num: number = await promise;
ts
const promise: Promise<number> = yyAsync();
const num: number = await promise;

Ví dụ, giả sử có 2 function takes3Seconds(), takes5Seconds() mất 3 giây và 5 giây để xử lý.

ts
async function takes3Seconds(): Promise<string> {
// ...
return "finished!";
}
 
async function takes5Seconds(): Promise<number> {
// ...
return -1;
}
ts
async function takes3Seconds(): Promise<string> {
// ...
return "finished!";
}
 
async function takes5Seconds(): Promise<number> {
// ...
return -1;
}

Nếu thực thi trực tiếp các function này sẽ mất 3 + 5 = 8 giây.

ts
const str: string = await takes3Seconds();
const num: number = await takes5Seconds();
ts
const str: string = await takes3Seconds();
const num: number = await takes5Seconds();

Sử dụng Promise.all() có thể viết như sau. Thời gian mất là thời gian của function lâu nhất, tức là 5 giây.

ts
const tuple: [string, number] = await Promise.all([
takes3Seconds(),
takes5Seconds(),
]);
ts
const tuple: [string, number] = await Promise.all([
takes3Seconds(),
takes5Seconds(),
]);

Lúc này biến tuple nhận giá trị trả về của Promise.all() có kiểu [string, number]. Phần generics Promise<T> của các function thực thi và thứ tự kiểu của tuple là nhất quán. Nghĩa là nếu đổi chỗ như sau, sẽ nhận được tuple [number, string] đã đổi chỗ.

ts
const tuple: [number, string] = await Promise.all([
takes5Seconds(),
takes3Seconds(),
]);
ts
const tuple: [number, string] = await Promise.all([
takes5Seconds(),
takes3Seconds(),
]);

Promise.all() không lưu vào tuple giá trị trả về theo thứ tự function kết thúc trước, mà giữ nguyên thứ tự ban đầu. Không phải vì take3seconds() kết thúc sớm hơn nên được lưu vào tuple trước, mà kiểu phần tử của tuple tuple được quyết định theo thứ tự truyền vào argument.