Hôm nay chúng ta sẽ lại nói về Promise
. Trong javascript chúng ta đã làm việc với nó rất nhiều, chẳng hạn như khi thao tác với API hoặc khi muốn tạo một xử lý bất đồng bộ. Promise
là một khái niệm không dễ hiểu mà cũng không khó hiểu. Nhưng tuy thuộc vào mức độ hiểu biết về nó sẽ cho chúng ta có nhiều lựa chọn hơn trong xử lý tình huống trong javascript. Hôm nay hãy cùng mình tìm hiểu về một cách để quản lý nhiều Promise
hoặc xử lý bất đồng bộ. Trong bài viết này chúng ta sẽ kết hợp dùng class
và Promise
.
1. class
và Promise
trong javascript
1.1 Class
Khái niệm class
được giới thiệu trong chuẩn ES6 của javascript dựa trên nền tảng kế thừa nguyên mẫu prototypal inheritance
có sẵn trong javascript. Cơ bản thì prototypal inheritance
đề cập đến khả năng truy cập các thuộc tính (properties) của một object A từ một object B khác (chúng ta sẽ xem xét vấn đề này trong một bài viết khác).
Thực tế class
là một function đặc biệt và khi gọi function ấy với từ khóa new
chúng ta sẽ nhận được một thể hiện của một object mà class
đó đã định nghĩa. Và khi khởi tạo với từ khóa new
thì method constructor
trong class đó sẽ được gọi (tại đây chúng ta có thể nhận tham số đầu vào và dùng trong class).
Trong bài viết hôm nay chúng ta sẽ sử dụng class
để lưu các biến dùng để quản lý nhiều Promise
.
1.2 Promise
Khái niệm Promise
đã quá quen thuộc với chúng ta rồi. Promise
đại diện cho một xử lý bất đồng bộ (xử lý cần chờ 1 khoảng thời gian lớn/nhỏ để hoàn thành).
Khi khởi tạo Promise
nó sẽ cần đầu vào là một callback function. Function này sẽ nhận hai tham số đầu vào lần lượt là resolve
và reject
. Hai tham số này sẽ cho phép chúng ta quyết định Promise
mà chúng ta khởi tạo sẽ thành công trả về dữ liệu (resolve
) hay là sẽ thất bại và trả về mã lỗi (reject
).
Một Promise khi được khởi chạy sẽ có thể có một trong 3 trạng thái là pending
, fulfilled
, rejected
. pending
là trạng thái chờ xử lý kết thúc, là trạng thái ban đầu của một Promise
. fulfilled
là trạng thái xử lý thành công, nó thể hiện rằng xử lý Promise
đã thành công (dùng resolve
để chuyển Promise
về trạng thái này). Cuối cùng là rejected
là trạng thái xử lý thất bại, nó thể hiện rằng xử lý Promise
đã thất bại (dùng reject
để chuyển Promise
về trạng thái này).
Một Promise
khi đã chạy sẽ chỉ có thể thành công hoặc thất bại (trừ trạng thái pending
ban đầu). Sau xử lý thành công hay thất bại nó sẽ trả về dữ liệu tương ứng khi thành công và mã lỗi khi thất bại. Chúng ta có thể dùng .then
để thao tác với dữ liệu thành công tương ứng và .catch
để thao tác với dữ liệu mã lỗi trả về.
Một cách nữa giúp chúng ta có thể dễ dàng trả về một Promise
thành công hoặc thất bại với dữ liệu thành công hoặc mã lỗi tương ứng. Promise.prototype.resolve
và Promise.prototype.reject
sẽ lần lượt trả về cho chúng ta một thể hiện Promise
thành công hoặc một thể hiện Promise
thất bại. Nó giúp chúng ta linh hoạt hơn trong cách xử lý tình huống, không phụ thuộc quá nhiều vào xử lý.
Tiếp đến hãy cùng thử kết hợp hai khái niệm bên trên để xử lý cho bài toán quản lý nhiều Promise
.
2. Kết hợp class
và Promise
Đầu tiên mục tiêu của chúng ta sẽ là quản lý nhiều xử lý bất đồng bộ cho phép handle lỗi cho nhiều xử lý này. Khi một xử lý xảy ra lỗi chúng ta sẽ tạm dừng những xử lý lỗi sau đó đồng thời chạy một xử lý trung gian khác (để gỡ lỗi) và cho đến khi xử lý này có kết quả thì sẽ bắt đầu chạy lại những xử lý lỗi đã tạm dừng trước đó và kết thúc một chuỗi xử lý.
Tình huống mà chúng ta có thể áp dụng thực tế chính là xử lý refresh session authen khi hết hạn. Tiếp đến hãy đến với đoạn code chúng ta sẽ thao tác:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 | <span class="token comment">// Chúng ta sẽ khai báo một điều kiện để handle lỗi</span> <span class="token comment">// Dưới đây là mã lỗi mong muốn khi xử lý bị lỗi của chúng ra trả về</span> <span class="token keyword">const</span> <span class="token constant">DESIRED_ERROR_CODE</span> <span class="token operator">=</span> <span class="token number">1000</span> <span class="token comment">// Chúng ta sẽ fake 1 xử lý bất đồng bộ</span> <span class="token comment">// Trả về một thể hiện Promise và điều khiển </span> <span class="token comment">// thành công hoặc thất bại bằng tham số "needReject"</span> <span class="token comment">// Khi lỗi chúng ta sẽ trả mã lỗi mà chúng ta mong muốn ở trên</span> <span class="token keyword">function</span> <span class="token function">doSomething</span><span class="token punctuation">(</span><span class="token parameter">config<span class="token punctuation">,</span> needReject</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">Promise</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">resolve<span class="token punctuation">,</span> reject</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token function">setTimeout</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'run "doSomething async"'</span><span class="token punctuation">)</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>needReject<span class="token punctuation">)</span> <span class="token function">resolve</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>needReject<span class="token punctuation">)</span> <span class="token function">reject</span><span class="token punctuation">(</span><span class="token punctuation">{</span> code<span class="token operator">:</span> <span class="token constant">DESIRED_ERROR_CODE</span><span class="token punctuation">,</span> message<span class="token operator">:</span> <span class="token string">'Some thing went wrong'</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token number">2000</span><span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token comment">// Tiếp đến tạo một class "AnyService"</span> <span class="token comment">// dùng để quản lý và handle lỗi.</span> <span class="token comment">// "isProcessing" dùng để làm tín hiệu là có 1 xử lý lỗi</span> <span class="token comment">// trước đó và các xử lý lỗi phía sau sẽ cần tạm dừng và chạy lại sau</span> <span class="token comment">// "queue" dùng để chứa những xử lý lỗi đang được tạm dừng</span> <span class="token comment">// và sẽ chạy lại khi xử lý trung gian gỡ lỗi trả về kết quả</span> <span class="token keyword">class</span> <span class="token class-name">AnyService</span> <span class="token punctuation">{</span> <span class="token function">constructor</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span>isProcessing <span class="token operator">=</span> <span class="token boolean">false</span> <span class="token keyword">this</span><span class="token punctuation">.</span>queue <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token punctuation">}</span> <span class="token comment">// Chúng ta sẽ cần một method để chạy lại những xử lý lỗi</span> <span class="token comment">// được tạm dừng trước đó</span> <span class="token comment">// Nếu là "isError" === true thì các xử lý lỗi trong "queue"</span> <span class="token comment">// sẽ đều phải trả về lỗi (trường hợp xử lý trung gian gỡ lỗi chạy nhưng không thành công)</span> <span class="token comment">// Ngược lại sẽ trả về dữ liệu mong muốn và sẽ là đầu vào cho</span> <span class="token comment">// những xử lý đã tạm dừng trước đó</span> <span class="token function">executeQueue</span><span class="token punctuation">(</span><span class="token parameter">isError<span class="token punctuation">,</span> data</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>isError<span class="token punctuation">)</span> <span class="token keyword">this</span><span class="token punctuation">.</span>queue<span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">[</span>_<span class="token punctuation">,</span> reject<span class="token punctuation">]</span></span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token function">reject</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>isError<span class="token punctuation">)</span> <span class="token keyword">this</span><span class="token punctuation">.</span>queue<span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter"><span class="token punctuation">[</span>resolve<span class="token punctuation">]</span></span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token function">resolve</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token comment">// Đây sẽ là phần xử lý handle chính</span> <span class="token comment">// Đầu tiên chúng ta sẽ bắt lỗi theo mã lỗi mong muốn `DESIRED_ERROR_CODE`</span> <span class="token comment">// Tiếp đến sẽ có 2 trường hợp có thể xảy ra</span> <span class="token comment">// 1. Có một xử lý lỗi được handle trước đó (isProcessing === true)</span> <span class="token comment">// => Trường hợp này thì những xử lý lỗi tương tự sau đó</span> <span class="token comment">// chúng ta sẽ đưa vào "queue"</span> <span class="token comment">// và sẽ xử lý khi method "executeQueue" được gọi (khi gỡ lỗi kết thúc)</span> <span class="token comment">// 2. Chưa có xử lý lỗi nào được handle trước đó</span> <span class="token comment">// => Trường hợp này chúng ta sẽ gọi một xử lý trung gian gỡ lỗi</span> <span class="token comment">// để lấy thêm thông tin để khi gọi lại xử lý bị lỗi đầu tiên</span> <span class="token comment">// và những xử lý lỗi đã tạm dừng trước đó một lần nữa</span> <span class="token comment">// thì sẽ không bị lỗi</span> <span class="token comment">// => Đồng thời ở tại đây chúng ta sẽ gọi "executeQueue" tương ứng</span> <span class="token comment">// với kết quả trả về từ xử lý trung gian (thành công hoặc thất bại)</span> <span class="token function">handleError</span><span class="token punctuation">(</span><span class="token parameter">error<span class="token punctuation">,</span> config</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>error<span class="token punctuation">.</span>code <span class="token operator">===</span> <span class="token constant">DESIRED_ERROR_CODE</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>isProcessing<span class="token punctuation">)</span> <span class="token punctuation">{</span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'currently processing => Need push to queue => Run after'</span><span class="token punctuation">,</span> config<span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">Promise</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">resolve<span class="token punctuation">,</span> reject</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span>queue<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span><span class="token punctuation">[</span>resolve<span class="token punctuation">,</span> reject<span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">data</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'execute: '</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token operator">...</span>config<span class="token punctuation">,</span> <span class="token operator">...</span>data <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">executeSomething</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token operator">...</span>config<span class="token punctuation">,</span> <span class="token operator">...</span>data <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">catch</span><span class="token punctuation">(</span><span class="token parameter">error</span> <span class="token operator">=></span> Promise<span class="token punctuation">.</span><span class="token function">reject</span><span class="token punctuation">(</span>error<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token keyword">this</span><span class="token punctuation">.</span>isProcessing <span class="token operator">=</span> <span class="token boolean">true</span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'processing...'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> a<span class="token operator">:</span> <span class="token number">4</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">executeSomething</span><span class="token punctuation">(</span><span class="token punctuation">{</span> a<span class="token operator">:</span> <span class="token number">4</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">data</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'this.queue'</span><span class="token punctuation">,</span> <span class="token keyword">this</span><span class="token punctuation">.</span>queue<span class="token punctuation">)</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">executeSomething</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token operator">...</span>config<span class="token punctuation">,</span> c<span class="token operator">:</span> <span class="token number">5</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">executeQueue</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> c<span class="token operator">:</span> <span class="token number">5</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token keyword">this</span><span class="token punctuation">.</span>isProcessing <span class="token operator">=</span> <span class="token boolean">false</span> Promise<span class="token punctuation">.</span><span class="token function">resolve</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">catch</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">error</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">executeQueue</span><span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span> <span class="token keyword">this</span><span class="token punctuation">.</span>isProcessing <span class="token operator">=</span> <span class="token boolean">false</span> Promise<span class="token punctuation">.</span><span class="token function">reject</span><span class="token punctuation">(</span>error<span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> Promise<span class="token punctuation">.</span><span class="token function">reject</span><span class="token punctuation">(</span>error<span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token comment">// Đây sẽ là method dùng để gọi khi đã khởi tạo class</span> <span class="token keyword">async</span> <span class="token function">executeSomething</span><span class="token punctuation">(</span><span class="token parameter">config<span class="token punctuation">,</span> needReject</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">try</span> <span class="token punctuation">{</span> <span class="token keyword">const</span> data <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">doSomething</span><span class="token punctuation">(</span>config<span class="token punctuation">,</span> needReject<span class="token punctuation">)</span> <span class="token keyword">return</span> Promise<span class="token punctuation">.</span><span class="token function">resolve</span><span class="token punctuation">(</span>config<span class="token punctuation">,</span> data<span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span>error<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">handleError</span><span class="token punctuation">(</span>error<span class="token punctuation">,</span> config<span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token comment">// Tại đây chúng ta sẽ khởi tạo một thể hiển của "AnyService"</span> <span class="token comment">// và call 3 xử lý lỗi ngay sau đó</span> <span class="token keyword">const</span> service <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">AnyService</span><span class="token punctuation">(</span><span class="token punctuation">)</span> service<span class="token punctuation">.</span><span class="token function">executeSomething</span><span class="token punctuation">(</span><span class="token punctuation">{</span> a<span class="token operator">:</span> <span class="token number">1</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token boolean">true</span><span class="token punctuation">)</span> service<span class="token punctuation">.</span><span class="token function">executeSomething</span><span class="token punctuation">(</span><span class="token punctuation">{</span> a<span class="token operator">:</span> <span class="token number">2</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token boolean">true</span><span class="token punctuation">)</span> service<span class="token punctuation">.</span><span class="token function">executeSomething</span><span class="token punctuation">(</span><span class="token punctuation">{</span> a<span class="token operator">:</span> <span class="token number">3</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token boolean">true</span><span class="token punctuation">)</span> <span class="token comment">// Kết quả nhận được sẽ như dưới</span> <span class="token comment">// run "doSomething async" with: {a: 1}</span> <span class="token comment">// processing... {a: 4}</span> <span class="token comment">// run "doSomething async" with: {a: 2}</span> <span class="token comment">// currently processing => Need push to queue => Run after {a: 2}</span> <span class="token comment">// run "doSomething async" with: {a: 3}</span> <span class="token comment">// currently processing => Need push to queue => Run after {a: 3}</span> <span class="token comment">// run "doSomething async" with: {a: 4}</span> <span class="token comment">// this.queue (2) [Array(2), Array(2)]</span> <span class="token comment">// execute: {a: 2, c: 5}</span> <span class="token comment">// execute: {a: 3, c: 5}</span> <span class="token comment">// run "doSomething async" with: {a: 1, c: 5}</span> <span class="token comment">// run "doSomething async" with: {a: 2, c: 5}</span> <span class="token comment">// run "doSomething async" with: {a: 3, c: 5}</span> |
Với đoạn code trên thì chúng ta đã tạm có thể xử lý theo như đúng yêu cầu đề ra ban đầu. Sẽ còn những vấn đề phát sinh xung quanh nhưng sẽ không đáng kể lắm.
Trong đoạn code trên chúng ta sẽ cần chú ý một điểm cốt lõi để việc quản lý những Promise
một cách mượt nhất. Đó chính là phần xử lý tạm dừng những xử lý lỗi khi mà trước đó đã có xử lý bị lỗi và gọi lại những xử lý lỗi đã tạm dừng. Ở đây chúng ta sẽ dùng phương pháp là ngoại trừ xử lý lỗi đầu tiên thì những xử lý lỗi ngay sau đó sẽ được tạo tương ứng với một thể hiện Promise
. Những thể hiện Promise
này sẽ làm công việc là lưu hai xử lý callback resolve
và reject
vào trong queue
và sẽ luôn ở trạng thái pending
trong suốt quá trình xử lý trung gian gỡ lỗi được gọi đến khi có kết quả (vì chúng ta không gọi resolve
hay reject
ngay sau đó mà sẽ chỉ chạy khi method executeQueue
được gọi).
Rất thú vị đúng không nào, bằng một cách nào đó chúng ta đã tạm dừng được những xử lý và còn có thể thao tác với những xử lý đó ngay sau khi xử lý trung gian gỡ lỗi có kết quả.
3. Kết luận
Vậy là mình đã trình bày xong việc kết hợp class
và Promise
dùng để quản lý và thao tác với những xử lý bất đồng bộ hay Promise
. Việc kết hợp này dựa khá nhiều vào khái niệm Promise
cũng như trạng thái của Promise
, một phần là là những biến của chúng ta được đồng bộ trong một thể hiện class
. Có lẽ lần sau hãy thử kết hợp Closures
và Promise
xem sao.
Bài viết của mình đến đây là hết rồi. Mong rằng nó sẽ đem lại lợi ích cho các bạn và có những hướng xử lý thú vị với Promise
. Hẹn gặp lại các bạn trong bài viết tiếp theo. Xin chào!