Đã bắt đầu một năm mới, và trong khi nhiều người hứa hẹn sẽ năng động hơn, tôi sẽ chỉ cho bạn cách làm cho Promise
trở nên lười biếng hơn…Promise
s, đó là.
Nó sẽ có ý nghĩa hơn trong một thời điểm.
Đầu tiên, hãy xem một ví dụ cơ bản về Promise
. Ở đây, tôi có một chức năng gọi là ngủ mất thời gian tính bằng mili giây và một giá trị. Nó trả về một lời hứa sẽ thực thi setTimeout
trong số mili giây mà chúng ta nên đợi; sau đó Lời hứa giải quyết với giá trị.
/** * @template ValueType * @param {number} ms * @param {ValueType} value * @returns {Promise<ValueType>} */ function sleep(ms, value) { return new Promise((resolve) => { setTimeout(() => resolve(value), ms); }); }
Nó hoạt động như thế này:
Chúng ta có thể đợi chức năng sleep
với các đối số 1000
và 'Yawn & stretch'
và sau một giây, console
sẽ ghi lại chuỗi 'Yawn & stretch'.
Không có gì quá đặc biệt về điều đó. Nó có thể hoạt động như bạn mong đợi, nhưng sẽ hơi lạ nếu chúng ta lưu trữ nó như một biến để await
sau này, thay vì await
Promise
được trả lại ngay lập tức.
const nap = sleep(1000, 'Yawn & stretch')
Bây giờ, giả sử chúng ta thực hiện một số công việc khác cần có thời gian (chẳng hạn như nhập ví dụ tiếp theo), rồi await
biến nap
.
Bạn có thể mong đợi độ trễ một giây trước khi giải quyết, nhưng trên thực tế, nó giải quyết ngay lập tức. Bất cứ khi nào bạn tạo một Promise
, bạn sẽ khởi tạo bất kỳ chức năng không đồng bộ nào mà nó chịu trách nhiệm.
Trong ví dụ của chúng tôi, thời điểm chúng tôi xác định biến nap
, Promise
được tạo để thực thi setTimeout
. Bởi vì tôi là một người đánh máy chậm, Promise
sẽ được giải quyết khi chúng tôi await
nó.
Nói cách khác, Promise
s háo hức. Họ không đợi bạn await
họ.
Trong một số trường hợp, đây là một điều tốt. Trong các trường hợp khác, nó có thể dẫn đến việc sử dụng tài nguyên không cần thiết. Đối với những trường hợp đó, bạn có thể muốn thứ gì đó trông giống như Promise
, nhưng sử dụng
Trước khi chúng ta tiếp tục, tôi muốn cho bạn thấy một điều thú vị.
Promise
s không phải là thứ duy nhất có thể được await
ed trong JavaScript. Nếu chúng ta tạo một Object
đơn giản bằng phương thức .then()
, chúng ta thực sự có thể await
đối tượng đó giống như bất kỳ Promise
nào.
Điều này hơi lạ, nhưng nó cũng cho phép chúng ta tạo các đối tượng khác nhau trông giống như Promise
nhưng không phải vậy. Những đối tượng này đôi khi được gọi là “
Với ý nghĩ đó, hãy tạo một cái mớiLazyPromise
mở rộng hàm tạo Promise
tích hợp sẵn. Việc mở rộng Promise không thực sự cần thiết, nhưng nó làm cho nó trông giống với Promise
hơn bằng cách sử dụng những thứ như instanceof
.
class LazyPromise extends Promise { /** @param {ConstructorParameters<PromiseConstructor>[0]} executor */ constructor(executor) { super(executor); if (typeof executor !== 'function') { throw new TypeError(`LazyPromise executor is not a function`); } this._executor = executor; } then() { this.promise = this.promise || new Promise(this._executor); return this.promise.then.apply(this.promise, arguments); } }
Phần cần tập trung vào là phương thức then()
. Nó chiếm đoạt hành vi mặc định của một Promise
tiêu chuẩn để đợi cho đến khi phương thức .then()
được thực thi trước khi tạo một Promise
thực sự.
Điều này tránh khởi tạo chức năng không đồng bộ cho đến khi bạn thực sự yêu cầu nó. Và Nó hoạt động cho dù bạn gọi rõ ràng .then()
hay sử dụng await
.
Bây giờ, hãy xem điều gì sẽ xảy ra nếu chúng ta thay thế Promise
trong chức năng sleep
ban đầu bằng LazyPromise
. Một lần nữa, chúng ta sẽ gán kết quả cho một biến nap
.
function sleep(ms, value) { return new LazyPromise((resolve) => { setTimeout(() => resolve(value), ms); }); } const nap = sleep(1000, 'Yawn & stretch')
Sau đó, chúng tôi dành thời gian để gõ dòng await nap
và thực hiện nó.
Lần này, chúng ta thấy độ trễ một giây trước khi Promise
được giải quyết bất kể thời gian đã trôi qua kể từ khi biến được tạo.
(Lưu ý rằng việc triển khai này chỉ tạo Promise
mới một lần và tham chiếu nó trong các lần gọi tiếp theo. Vì vậy, nếu chúng ta await
nó một lần nữa, nó sẽ giải quyết ngay lập tức giống như bất kỳ Promise
bình thường nào)
Tất nhiên, đây là một ví dụ tầm thường mà bạn có thể sẽ không tìm thấy trong mã sản xuất, nhưng có nhiều dự án sử dụng các đối tượng giống như Promise
- được đánh giá lười biếng. Có lẽ ví dụ phổ biến nhất là với cơ sở dữ liệu ORM s và trình tạo truy vấn như
Hãy xem xét mã giả dưới đây. Nó được lấy cảm hứng từ một số trình tạo truy vấn sau:
const query = db('user') .select('name') .limit(10) const users = await query
Chúng tôi tạo một truy vấn cơ sở dữ liệu chuyển đến bảng "user"
và chọn mười mục nhập đầu tiên và trả về tên của chúng. Về lý thuyết, điều này sẽ hoạt động tốt với Promise
thông thường.
Nhưng nếu chúng ta muốn sửa đổi truy vấn dựa trên các điều kiện nhất định như tham số chuỗi truy vấn thì sao? Sẽ thật tuyệt nếu có thể tiếp tục sửa đổi truy vấn trước khi chờ Promise
.
const query = db('user') .select('name') .limit(10) if (orderBy) { query.orderBy(orderBy) } if (limit) { query.limit(limit) } if (id) { query.where({ id: id }) } const users = await query
Nếu truy vấn cơ sở dữ liệu ban đầu là một Promise
tiêu chuẩn, nó sẽ háo hức khởi tạo truy vấn ngay khi chúng ta gán biến và chúng ta sẽ không thể sửa đổi truy vấn đó sau này.
Với đánh giá lười biếng, chúng tôi có thể viết mã như thế này dễ theo dõi hơn, cải thiện trải nghiệm của nhà phát triển và chỉ thực hiện truy vấn một lần khi chúng tôi cần.
Đó là một ví dụ mà đánh giá lười biếng là tuyệt vời. Nó cũng có thể hữu ích cho những việc như xây dựng, sửa đổi và sắp xếp các yêu cầu HTTP.
Lazy Promise
s rất tuyệt vời cho các trường hợp sử dụng phù hợp, nhưng điều đó không có nghĩa là chúng nên thay thế mọi Promise
. Trong một số trường hợp, sẽ rất hữu ích nếu bạn háo hức khởi tạo và chuẩn bị sẵn phản hồi càng sớm càng tốt.
Đây là một trong những tình huống “còn tùy”. Nhưng lần tới khi ai đó yêu cầu bạn thực hiện một Promise
, hãy cân nhắc việc lười biếng thực hiện nó ( ͡° ͜ʖ ͡°).
Cảm ơn bạn rất nhiều vì đã đọc. Nếu bạn thích bài viết này, xin vui lòng
Được xuất bản lần đầu vào