- vừa được xem lúc

Tự code lại JavaScript Promise từ đầu

0 0 5

Người đăng: TechSum

Theo Viblo Asia

Hôm nay mình đã thử tự code Promise trong JavaScript theo chuẩn Promises/A+ từ đầu để kiểm tra khả năng của mình. Sẵn tiện viết luôn bài này chia sẻ cách làm cho những bạn nào có quan tâm. Bắt đầu luôn nha.

Hãy sẽ gọi Promise của chúng ta là NotNow. Những cái tên bắt tai như will, future, later, v.v. đã bị dùng bởi mấy package khác rồi.

Khởi đầu đơn giản thôi.

const PENDING = "pending";
const FULFILLED = "fulfilled"; class NotNow { #state = PENDING; #onFulfilleds = []; #value; constructor(fn) { fn(this.#fulfill.bind(this)); } #fulfill(value) { this.#state = FULFILLED; this.#value = value; this.#onFulfilleds.forEach((fn) => fn(value)); } then(onFulfilled) { this.#addOnFulfilled(onFulfilled); } #addOnFulfilled(onFulfilled) { if (this.#state === PENDING) { this.#onFulfilleds.push(onFulfilled); } else if (this.#state === FULFILLED) { onFulfilled(this.#value); } }
}

constructor sẽ thực thi function bạn truyền vào, với phương thức #fulfill là callback để bạn trả về kết quả.

Phương thức #fulfill lưu lại giá trị trả về và kích hoạt các callback với giá trị đó.

Phương thức then thêm callback để thực thi khi promise nhận được giá trị trả về. Bên trong phương thức này gọi phương thức #addOnFulfilled.

Phương thức #addOnFulfilled, nếu promise chưa nhận được giá trị sẽ thêm callback vào để thực thi khi promise nhận được giá trị, còn nếu promise đã có giá trị thì thực thi callback luôn.

Test thử nào.

const notNow = new NotNow((fulfill) => { setTimeout(() => fulfill(2), 2000);
});
notNow.then((value) => console.log(`callback 1, fulfilled with ${value}`));
notNow.then((value) => console.log(`callback 2, fulfilled with ${value}`));

Sau 2 giây, màn hình sẽ in ra:

callback 1, fulfilled with 2
callback 2, fulfilled with 2

Ok. Bước đầu tạm ổn.

Thực tế thì các callback không được thực thi một cách đồng bộ ngay khi có kết quả trả về, mà sẽ thực thi bất đồng bộ. Điều này để đảm bảo tính nhất quán: cho dù promise có nhận được giá trị một cách đồng bộ, thì các callback vẫn được thực thi một cách bất đồng bộ. Bạn có thể dùng setTimeout hay queueMicrotask đều được. Mình sẽ dùng queueMicrotask để các callback được thực thi sớm hơn.

Sửa lại phương thức #fulfill:

// this.#onFulfilleds.forEach((fn) => fn(value));
queueMicrotask(() => this.#onFulfilleds.forEach((fn) => fn(value)));

Sửa lại phương thức #addOnFulfilled:

// onFulfilled(this.#result);
queueMicrotask(() => onFulfilled(this.#value));

Mỗi khi thêm callback cho promise, thực ra chúng ta đang tạo ra một promise mới. Promise mới này chính là kết quả khi thực thi callback. Chúng ta sau đó có thể truyền promise mới này đến những nơi khác trong chương trình để có thể thêm các callback tiếp tục xử lý kết quả của callback hiện tại.

Vậy thì trong phương thức then, chúng ta sẽ tạo ra một promise mới. Khi promise hiện tại nhận được giá trị, chúng ta thực thi callback, rồi trả về cho promise mới kết quả của callback.

class NotNow { // ... then(onFulfilled) { return new Promise((nextFulfill) => { this.#addOnFulfilled((value) => { const nextValue = onFulfilled(value); nextFulfill(nextValue); }); }); } // ...
}

Test thử luôn.

const a = new NotNow((fulfill) => { setTimeout(() => fulfill(2), 2000);
});
const b = a.then((value) => value * 2);
b.then((value) => console.log(value));

a sẽ nhận được giá trị 2, rồi b sẽ nhận được giá trị 2 * 2 là 4. Vậy 4 sẽ được in ra màn hình.

Chúng ta có thể gọi nối tiếp then:

new NotNow((fulfill) => { setTimeout(() => fulfill(2), 2000);
}) .then((value) => value * 2) .then((value) => console.log(value));

Nối thêm nhiều lần cũng được luôn:

new NotNow((fulfill) => { setTimeout(() => fulfill(2), 2000);
}) .then((value) => value * 2) .then((value) => value * 2) .then((value) => value * 2); .then((value) => console.log(value));

Ahh, method chaining nhìn cứ thấy dễ chịu nhỉ.

Một đặc tính của promise là khi giá trị trả về cho promise cũng là một promise (là cũng có phương thức then), thì promise hiện tại sẽ cố nhận lấy giá trị trả về của promise đó luôn. Ví dụ:

const a = new Promise((fulfill) => fulfill(2));
const b = new Promise((fulfill) => fulfill(a));
b.then((value) => console.log(value));

Kết quả in ra sẽ là:

2

Mặc dù chúng ta đã trả cho b giá trị là a, nó vẫn cố nhận lấy luôn giá trị trả về của a là 2, bởi vì a là một promise.

Kết quả cũng vẫn vậy dù có có lồng qua nhiều lớp promise:

const a = new Promise((fulfill) => fulfill(2));
const b = new Promise((fulfill) => fulfill(a));
const c = new Promise((fulfill) => fulfill(b));
const d = new Promise((fulfill) => fulfill(c));
d.then((value) => console.log(value));

Kết quả:

2

Để làm được điều này, khi nhận được giá trị trong phương thức #fulfill, kiểm tra nếu giá trị đó là promise thì chúng ta không thực thi các callback ngay, mà đợi cho đến khi promise đó cũng nhận được giá trị luôn, bằng cách thêm #fulfill vào promise đó như một callback.

class NotNow { //... #fulfill(value) { if (typeof value?.then === "function") { return value.then(this.#fulfill); } this.#state = FULFILLED; this.#value = value; queueMicrotask(() => this.#onFulfilleds.forEach((fn) => fn(value))); } //...
}

Lần tiếp theo khi #fulfill được gọi, nó cũng thực thi tương tự, nên nó hoạt động một cách đệ quy tới khi giá trị nhận được không phải là promise.

Kiểm tra luôn:

const a = new Promise((fulfill) => fulfill(2));
const b = new Promise((fulfill) => fulfill(a));
const c = new Promise((fulfill) => fulfill(b));
const d = new Promise((fulfill) => fulfill(c));
d.then((value) => console.log(value));

Kết quả:

2

Chính xác!

Có thể dùng async/await với NotNow không? Tất nhiên rồi! Giá trị được awaitchỉ cần hoạt động như một promise, class thực sự của nó không phải thành vấn đề.

const a = await new NotNow((fulfill) => { setTimeout(() => fulfill(5), 2000);
});
console.log(a);

Đại khái là vậy. Trong bài viết này mình đã tạm qua xử lý lỗi cho đơn giản, để dễ tập trung vào các vấn đề cốt lõi. Hãy tham khảo phiên bản đầy đủ trong source code. Ngoài ra trong đó còn có các phương thức bổ trợ như all, allSettled, any, race.

Mong là bài viết hữu ích. Hãy thoải mái chia sẻ suy nghĩ, góp ý, hoặc cả cách làm của bạn, ở phần bình luận.

Bình luận

Bài viết tương tự

- vừa được xem lúc

Giới thiệu Typescript - Sự khác nhau giữa Typescript và Javascript

Typescript là gì. TypeScript là một ngôn ngữ giúp cung cấp quy mô lớn hơn so với JavaScript.

0 0 500

- vừa được xem lúc

Bạn đã biết các tips này khi làm việc với chuỗi trong JavaScript chưa ?

Hi xin chào các bạn, tiếp tục chuỗi chủ đề về cái thằng JavaScript này, hôm nay mình sẽ giới thiệu cho các bạn một số thủ thuật hay ho khi làm việc với chuỗi trong JavaScript có thể bạn đã hoặc chưa từng dùng. Cụ thể như nào thì hãy cùng mình tìm hiểu trong bài viết này nhé (go).

0 0 414

- vừa được xem lúc

Một số phương thức với object trong Javascript

Trong Javascript có hỗ trợ các loại dữ liệu cơ bản là giống với hầu hết những ngôn ngữ lập trình khác. Bài viết này mình sẽ giới thiệu về Object và một số phương thức thường dùng với nó.

0 0 136

- vừa được xem lúc

Tìm hiểu về thư viện axios

Giới thiệu. Axios là gì? Axios là một thư viện HTTP Client dựa trên Promise.

0 0 117

- vừa được xem lúc

Imports và Exports trong JavaScript ES6

. Giới thiệu. ES6 cung cấp cho chúng ta import (nhập), export (xuất) các functions, biến từ module này sang module khác và sử dụng nó trong các file khác.

0 0 93

- vừa được xem lúc

Bài toán đọc số thành chữ (phần 2) - Hoàn chỉnh chương trình dưới 100 dòng code

Tiếp tục bài viết còn dang dở ở phần trước Phân tích bài toán đọc số thành chữ (phần 1) - Phân tích đề và những mảnh ghép đầu tiên. Bạn nào chưa đọc thì có thể xem ở link trên trước nhé.

0 0 229