-
Notifications
You must be signed in to change notification settings - Fork 0
/
1-2-6.html
1529 lines (1508 loc) · 48.8 KB
/
1-2-6.html
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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<!DOCTYPE html>
<html lang="zh-TW">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" href="./public/favicon.ico" />
<meta http-equiv="cache-control" content="no-cache" />
<title></title>
<link rel="stylesheet" href=" https://necolas.github.io/normalize.css/8.0.1/normalize.css" />
<link rel="stylesheet" href="./hightlight/default.min.css" />
<link rel="stylesheet" href="./css/main.css" />
<link rel="stylesheet" href="./css/copybutton.css" />
<link rel="stylesheet" href="./css/hightlight.css" />
<script src="./hightlight/hightlight.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/clipboard.js/2.0.11/clipboard.min.js"></script>
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-BEVZJDBC7Z"></script>
<script src="./js/gtag.js"></script>
</head>
<body>
<header>
<nav>
<h1>
<span id="toggle-menu"></span>
<a href="index.html"></a>
</h1>
</nav>
</header>
<main>
<aside>
<nav></nav>
</aside>
<article>
<h2 id="1-2-6">1-2-6 Promise 深入解析</h2>
<h3>Promise 化一個 API</h3>
<p>微信小程式環境中發送一個網路請求時會使用 路請求時會使用 wx.request()。</p>
<pre><code class="language-js">
wx.request({
url: 'test', // 範例
data: {
x: '',
y: ''
},
header: {
'content-type': 'application/json' // 預設值
},
success(res) {
console.log(res)
}
})
</code></pre>
<p>
這樣的設計有一個小問題,就是容易出現「回呼地獄」問題。如果我們想先透過,userInfo
介面來取得登入使用者資訊資料,再從登入使用者資訊資料中透過請求 ./${id}/friendList
介面來取得登入使用者的所有好友清單,那麼就需要執行以下程式。
</p>
<pre><code class="language-js">
wx.request({
url: './userInfo',
success(res) {
const id = res.data.id
wx.request({
url: `./${id}/friendList`,
success(res) {
console.log(res)
}
})
}
})
</code></pre>
<p>
解決「回呼地獄」問題的極佳工具就是 Promise,所以將微信小程式wx.request() 方法進行Promise
化,程式將變為如下所示的樣子。
</p>
<pre><code class="language-js">
const wxRequest = (url, data = {}, method = 'GEI') =>
new Promise((resolve, reject) => {
wx.request({
uri,
data,
method,
header: {
// 通用化header設定
},
success: function (res) {
const code = res.statusCode
if (code !== 200) {
reject({ error: 'request fail', code })
return
}
resolve(res.data)
},
fail: function (res) {
reject({ error: 'request fail' })
}
})
})
</code></pre>
<p>Promise 化更多類似(透過 success 和 fail代表狀態)的介面,程式如下。</p>
<pre><code class="language-js">
const promisify = (fn) => (args) =>
new Promise((resolve, reject) => {
args.success = function (res) {
return resolve(res)
}
args.fail = function (res) {
return reject(res)
}
})
</code></pre>
<p>在將 Promise 化的程式封裝為 promisify 之後,我們便可以像以下程式所示的 那樣使用。</p>
<pre><code class="language-js">
const wxRequest = promisify(wx.request)
</code></pre>
<p>
透過以上實例,我們知道. Promise 其實就是一個建構函數,我們可以使用這個建構函數建立一個
Promise 寶例。該建構函數很簡單,它只有一個參數,按照 Promise/A+ 標準的命名。我們把 Promise
建構函數的參數叫作 executor:區是函數類型的參數。這個函數又「自動」具有 resolve 、reject
兩個方法作為參數。
</p>
<p>我們可以根據此結論開始實現 Promise 的第一步,先簡單地實現一個建構函數,程式如下:</p>
<pre><code class="language-js">
function Promise (executor) {
}
</code></pre>
<p>
在上面的 wx.request() 介紹中,我們將其進行了 Promise
化,因此在巢狀結構回呼場景中可以使用以下方法。
</p>
<pre><code class="language-js">
wxRequest('./userInfo')
.then(
(data) => wxRequest(`./${data.id}/friendList`),
(error) => console.log(error)
)
.then(
(data) => {
console.log(data)
},
(error) => {
console.log(error)
}
)
</code></pre>
<p>
Promise 建構函數傳回一個 Promise 物件實例,這個傳回的 Promise 物件具有一個then
方法。在then 方法中,呼叫者可以定義兩個參數,分別是 onfulfilled 和
onrejected,它們都是函數類型的參數。其中,onfulfilled 透過參數可以取得 Promise 物件經過
resolve 處理後的值,onrejected 可以取得 Promise 物件經過 reject
處理後的值。透過這個值,我們來處理非同步作業完成後的邏輯。
</p>
<p>
這些都是 Promise/A+ 標準的基本內容,接下來我們繼續實現 Promise。在已有 Promise
建構函數的基礎上加上 then 原型方法的骨架,程式如下。
</p>
<pre><code class="language-js">
function Promise (executor) {
}
Promise.prototype.then = function (onfulfilled, onrejected) {
}
</code></pre>
<p>下面先來看一個範例,從範例中了解 Promise 的重點內容。</p>
<pre><code class="language-js">
let promise1 = new Promise((resolve, reject) => {
resolve('data')
})
promise1.then((data) => {
console.log(data)
})
let promise2 = new Promise((resolve, reject) => {
reject('error')
})
promise2.then(
(data) => console.log(data),
(error) => {
console.log(error)
}
)
</code></pre>
<p>
在使用 new 關鍵字呼叫 Promise 建構函數時,在合適的時機(常常是非同步作業結束時)呼叫
executor 的參數 resolve,並將經過 resolve 處理後的值作為 resolve
的函數參數執行,這個值便可以在後續 then
方法的第一個函數參數(onfulfilled)中拿到;同理,在出現錯誤時,呼叫 executor 的參數
reject,並將錯誤訊息作為 reject 的函數參數執行,這個錯誤訊息可以在後續then
方法的第二個函數參數(onrejected)中獲得。
</p>
<p>
因此,我們在實現Promise 時,應該有兩個變數,分別儲存經過 resolve 處理後的值,以及經過
reject 處理後的值(當然,因為 Promise狀態的唯一性,不可能同時出現經過 resolve
處理後的值和經過 reject
處理後的值,因此也可以用一個變數來儲存);同時還需要存在一個狀態,這個狀態就是 Promise
實例的狀態(pending、fulfilled、rejected);最後要提供 resolve 方法及 reject方
法,兩個方法需要作為 executor 的參數提供給開發者使用,程式如下。
</p>
<pre><code class="language-js">
function Promise(executor) {
const self = this
this.status = 'pending'
this.value = null
this.reason = null
function resolve(value) {
self.value = value
}
function reject(reason) {
self.reason = reason
}
executor(resolve, reject)
}
Promise.prototype.then = function (
onfulfilled = Function.prototype,
onrejected = Function.prototype
) {
onfulfilled(this.value)
onrejected(this.reason)
}
</code></pre>
<p>
為了確保 onfulfilled、onrejected
能夠穩固地執行,我們為其設定了預設值,其預設值為一個函數元(Function.prototype)。
</p>
<p>
注意,因為 resolve
的最後呼叫是由開發者在不確定環境下(常常是在全域中)直接呼叫的,因此為了在 resolve
函數中能夠拿到 Promise實例的值,我們需要對 this 進行儲存,上述程式中使用了 self變數來記錄
this,也可以使用箭頭函數來確保 this 執行的準確性。
</p>
<pre><code class="language-js">
function Promise(executor) {
this.status = 'pending'
this.value = null
this.reason = null
const resolve = (value) => {
this.value = value
}
const reject = (reason) => {
this.reason = reason
}
executor(resolve, reject)
}
Promise.prototype.then = function (
onfulfilled = Function.prototype,
onrejected = Function.prototype
) {
onfulfilled(this.value)
onrejected(this.reason)
}
</code></pre>
<p>
每個 Promise 實例的 then
方法邏輯都是一致的,實例在呼叫該方法時,可以透過原型(Promise.prototype)來呼叫,而不需要每次產生實體都新建立一個
then 方法,以便節省記憶體,顯然更合適。
</p>
<h3>Promise 實現狀態增強</h3>
<pre><code class="language-js">
let promise = new Promise((resolve, reject) => {
resolve('data')
reject('error')
})
promise.then(
(data) => {
console.log(data)
},
(error) => {
console.log(error)
}
)
</code></pre>
<p>
以上程式只會輸出 data。我們知道,Promise 實例的狀態只能從 pending 變為 fulfilled,或從
pending 變為 rejected。狀態一旦愛更完畢,就不可再次變化或逆轉。也就是說,如果一旦變為
fulfilled,就不能再變為 rejected;一旦轉為 rejected ,就不能再變為 fulfilled。
</p>
<p>
而上段程式實現顯然無法滿足這一特性。要執行上一段程式輸出 data 及
error,需要對狀態進行判斷和增強,如下。
</p>
<pre><code class="language-js">
function Promise(executor) {
this.status = 'pending'
this.value = null
this.reason = null
const resolve = (value) => {
if (this.status === 'pending') {
this.value = value
this.status = 'fulfilled'
}
}
const reject = (reason) => {
if (this.status === 'pending') {
this.reason = reason
this.status = 'rejected'
}
}
executor(resolve, reject)
}
Promise.prototype.then = function (onfulfilled, onrejected) {
onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : (data) => data
onrejected =
typeof onrejected === 'function'
? onrejected
: (error) => {
throw error
}
if (this.status === 'fulfilled') {
onfulfilled(this.value)
}
if (this.status === 'rejected') {
onrejected(this.reason)
}
}
</code></pre>
<p>
可以看到:resolve和 reject 方法中加入了判斷,只允許 Promise 實例狀態從 pending 變為
fulfilled,或從 pending 變為 rejected。
</p>
<p>
同時注意、這裡對 Promise.prototype.then 的參數 onfulfilled 和 onrejected
進行了判斷,當實際參數不是函數類型時,就需要指定預設函數值。這時的預設值不再是函數元
Function.prototype 了。
</p>
<p>
不過 Promise
是用來解決非同步問題的,而我們的程式全部都是同步執行的,似乎還差了更重要的邏輯。
</p>
<h3>非同步實現增強</h3>
<pre><code class="language-js">
let promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('data')
}, 2000)
})
promise.then((data) => {
console.log(data)
})
</code></pre>
<p>
正常來講,上述程式會在 2s 後輸出 data,但是現在,程式並沒有輸出任何資訊。這是為什麼呢?
</p>
<p>
原因很簡單,因為我們的實現邏輯全是同步的。上述程式在產生實體一個 Promise
的建構函數時,會在 setTimeout 邏輯中呼叫 resolve,也就是說,2s後才會呼叫 resolve
方法,更改 Promise 實例狀態。而結合我們的實現,then 方法中的 onfulfilled
是同步執行的,它在即時執行 this.status 仍然為 pending,並沒有做到「2s後再執行
onfulfilled」。
</p>
<p>
那該怎麼辦呢?我們似乎應該在合適的時間去呼叫
onfulfilled方法,這個合適的時間應該是開發者呼叫 resolve的時刻,那麼我們先在狀態(status)為
pending 時把開發者傳進來的 onfulfilled 方法存起來,再在 resolve
方法中執行即可。程式如下所示。
</p>
<pre><code class="language-js">
function Promise(executor) {
this.status = 'pending'
this.value = null
this.reason = null
this.onFulfilledFunc = Function.prototype
this.onRejectedFunc = Function.prototype
const resolve = (value) => {
if (this.status === 'pending') {
this.value = value
this.status = 'fulfilled'
this.onFulfilledFunc(this.value)
}
}
const reject = (reason) => {
if (this.status === 'pending') {
this.reason = reason
this.status = 'rejected'
this.onRejectedFunc(this.reason)
}
}
executor(resolve, reject)
}
Promise.prototype.then = function (onfulfilled, onrejected) {
onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : (data) => data
onrejected =
typeof onrejected === 'function'
? onrejected
: (error) => {
throw error
}
if (this.status === 'fulfilled') {
onfulfilled(this.value)
}
if ((this, status === 'rejected')) {
onrejected(this.reason)
}
if (this.status === 'pending') {
this.onFulfilledFunc = onfulfilled
this.onRejectedFunc = onrejected
}
}
</code></pre>
<p>
透過測試發現,我們實現的程式也可以支援非同步執行了!同時,我們知道promise
是非同步執行的!再來看一個實例,請判斷以下程式的輸出結果。
</p>
<pre><code class="language-js">
let promise = new Promise((resolve, reject) => {
resolve('data')
})
promise.then((data) => {
console.log(data)
})
console.log(1)
</code></pre>
<p>正常的話,這裡會按照順序先輸出1,再輸出 data。</p>
<p>
而我們實現的程式卻沒有考慮這種情況,實際先輸出了data,再輸出 1。 因此,需要將 resolve 和
reject 的執行放到任務佇列中。這裡姑且先放到 setTimeout
中,確保非同步執行(這樣的做法並不嚴謹,為了確保 Promise 屬於 microTasks,很多Promise
的實現函數庫用了 MutationObserver 來模仿 nextTick()。
</p>
<pre><code class="language-js">
const resolve = (value) => {
if (value instanceof Promise) {
return value.then(resolve, reject)
}
setTimeout(() => {
if (this.status === 'pending') {
this.value = value
this.status = 'fulfilled'
this.onFulfilledFunc(this.value)
}
})
}
const reject = (reason) => {
setTimeout(() => {
if (this.status === 'pending') {
this.reason = reason
this.status = 'rejected'
this.onRejectedFunc(this.reason)
}
})
}
executor(resolve, reject)
</code></pre>
<p>
這樣一來,在執行到 executor(resolve, reject)時,也能確保在 nextTick 中才去執行 Promise
被決議後的任務,不會阻塞同步任務。
</p>
<p>
同時,我們在 resolve 方法中加入了對 value 值是否為一個 Promise
實例的判斷敘述。到目前為止,整個實現程式如下所示。
</p>
<pre><code class="language-js">
function Promise(executor) {
this.status = 'pending'
this.value = null
this.reason = null
this.onFulfilledFunc = Function.prototype
this.onRejectedFunc = Function.prototype
const resolve = (value) => {
if (value instanceof Promise) {
return value.then(resolve, reject)
}
setTimeout(() => {
if (this.status === 'pending') {
this.value = value
this.status = 'fulfilled'
this.onFulfilledFun(this.value)
}
})
}
const reject = (reason) => {
setTimeout(() => {
if (this.status === 'pending') {
this.reason = reason
this.status = 'rejected'
this.onRejectedFunc(this.reason)
}
})
}
executor(resolve, reject)
}
Promise.prototype.then = function (onfulfilled, onrejected) {
onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : (data) => data
onrejected =
typeof onrejected === 'function'
? onrejected
: (error) => {
throw error
}
if (this.status === 'fulfilled') {
onfulfilled(this.value)
}
if (this.status === 'rejected') {
onrejected(this.reason)
}
if (this.status == 'pending') {
this.onFulfilledFunc = onfulfilled
this.onRejectedFunc = onrejected
}
}
</code></pre>
<p>下面的實現也會按照順序,先輸出1,再輸出data。</p>
<pre><code class="language-js">
let promise = new Promise((resolve, reject) => {
resolve('data')
})
promise.then((data) => {
console.log(data)
})
console.log(1)
</code></pre>
<h3>Promise 細節增強</h3>
<p>
到此為止,我們的Promise 實現似乎越來越可靠了,但是還有些細節需要增強。 舉例來說,在
Promise 實例狀態變更之前增加多個then 方法。
</p>
<pre><code class="language-js">
let promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('data')
}, 2000)
promise.then((data) => {
console.log(`1: ${data}`)
})
promise.then((data) => {
console.log(`2: $(data)`)
})
})
// 預期會有以下輸出
// 1:data
// 2:data
</code></pre>
<p>
而我們的實現只會輸出 2:data,這是因為第二個 then 方法中的 onFulfilledFunc 會覆蓋第一個
then 方法中的 onFulfilledFunc。
</p>
<p>
這個問題也好解決,只需要將所有 then 方法中的 onFulfilledFunc 儲存到一個陣列
onFulfilledArray 中,在目前 Promise 被決議時依次執行
onFulfilledArray陣列內的方法即可。對於 onRejectedFunc 同理,改動後的實現程式如下。
</p>
<pre><code class="language-js">
function Promise(executor) {
this.status = 'pending'
this.value = null
this.reason = null
this.onFulfilledArray = []
this.onRejectedArray = []
const resolve = (value) => {
if (value instanceof Promise) {
return value.then(resolve, reject)
}
setTimeout(() => {
if (this.status === 'pending') {
this.value = value
this.status = 'fulfilled'
this.onFulfilledArray.forEach((func) => {
func(value)
})
}
})
}
const reject = (reason) => {
setTimeout(() => {
if (this.status === 'pending') {
this.reason = reason
this.status = 'rejected'
this.onRejectedArray.forEach((func) => {
func(reason)
})
}
})
}
executor(resolve, reject)
}
Promise.prototype.then = function (onfulfilled, onrejected) {
onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : (data) => data
onrejected =
typeof onrejected === 'function'
? onrejected
: (error) => {
throw error
}
if (this.status === 'fulfilled') {
onfulfilled(this.value)
}
if (this.status === 'rejected') {
onrejected(this.reason)
}
if (this.status === 'pending') {
this.onFulfilledArray.push(onfulfilled)
this.onRejectedArray.push(onrejected)
}
}
</code></pre>
<p>
另外一個需要完整的細節是,在建構函數中如果出錯,將自動觸發 Promise 實例狀態變為
rejected,因此我們用 try..catch 區塊對 executor 進行包裹,如下。
</p>
<pre><code class="language-js">
try {
executor(resolve, reject)
} catch (e) {
reject(e)
}
</code></pre>
<p>當我們故意將程式錯寫為以下形式時:</p>
<pre><code class="language-js">
let promise = new Promise((resolve, reject) => {
setTout(() => {
resolve('data')
}, 2000)
})
promise.then(
(data) => {
console.log(data)
},
(error) => {
console.log('got error from promise', error)
}
)
// 就可以對錯誤進行處理,並捕捉到以下異常。
// got error from promise ReferenceError: setTimeouteout is not defined
// at <anonymous>: 2:3
// at <anonymous>: 33:7
</code></pre>
<p>
到目前為止,我們已經初步實現了基本的
Promise。獲得一個好的實現結果固然重要,但是在實現過程中,我們也加深了對 Promise
的了解,得出下面一些重要的結論。
</p>
<ul>
<li>Promise 的狀態具有凝固性。</li>
<li>Promise 可以在 then 方法第二個參數中進行錯誤處理。</li>
<li>Promise 實例可以增加多個 then 處理場景。</li>
</ul>
<h3>Promise then 的鏈式呼叫</h3>
<pre><code class="language-js">
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('lucas')
}, 2000)
})
promise
.then((data) => {
console.log(data)
return `${data} next then`
})
.then((data) => {
console.log(data)
})
</code></pre>
<p>這段程式執行後,將在2s後輸出 lucas,緊接著輸出 lucas next then。</p>
<p>
我們看到,Promise 實例的 then 方法支援鏈式呼叫,輸出經過 resolve 處理的 値後,如果在 then
方法區塊的 onfulfilled 函數中同步顯性傳回新的値,則將 在新 Promise 實例 then 方法的
onfulfilled 函數中輸出新値。
</p>
<p>
如果在第一個 then 方法區塊的 onfulfilled 函數中傳回另一個Promise
實例,結果又將如何呢?請看以下程式。
</p>
<pre><code class="language-js">
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('lucas')
}, 2000)
})
promise
.then((data) => {
console.log(data)
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('$ {data} next then')
}, 4000)
})
})
.then((data) => {
console.log(data)
})
</code></pre>
<p>上述程式將在 2s後輸出 lucas,緊接著再過4(第6s)輸出 lucas next then。</p>
<p>
由此可知,一個 Promise 實例 then 方法的 onfulfilled 函數和 onrejected
函數是支援再次傳回一個Promise 實例的,也支援傳回一個非 Promise
實例的普通值;並且,傳回的這個Promise 寶例或這個非 Promise 賣例的普通值將傳給下一個then
方法的 onfulfilled函數或 onrejected 函數,這樣,then 方法就支援鏈式呼叫了。
</p>
<h3>鏈式呼叫的初步實現</h3>
<p>
讓我們來分析一下,是不是為了能夠支援then 方法的鏈式呼叫,每一個then 方法的 onfulfilled
函數和 onrejected 函數都應該傳回一個 Promise 實例。 我們一步一步來分析,先看一個實際使用
Promise 鏈的場景,程式如下。
</p>
<pre><code class="language-js">
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('lucas')
}, 2000)
})
promise
.then((data) => {
console.log(data)
return `${data} next then`
})
.then((data) => {
console.log(data)
})
</code></pre>
<p>
這種 onfulfilled 函數會傳回一個普通字串類型的基本值,這裡的 onfulfilled 函數的程式如下。
</p>
<pre><code class="language-js">
(data) => {
console.log(data)
return `${data} next then`
}
</code></pre>
<p>
在前面實現的then 方法中,我們可以建立一個新的 Promise 資例,周promise2,並最後將這個
promise2 傳回,程式如下。
</p>
<pre><code class="language-js">
Promise.prototype.then = function (onfulfilled, onrejected) {
onfulfilled =
typeof onfulfilled == 'function'
? onfulfilled
: (data) => {
data
}
onrejected =
typeof onrejected == 'function'
? onrejected
: (error) => {
throw error
}
// promise2將作為then方法的傳回值
let promise2
if (this.status === 'fulfilled') {
return (promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
try {
//這個新的 promise2 resolved的值為 onfulfilled 的執行結果
let result = onfulfilled(this.value)
resolve(result)
} catch (e) {
reject(e)
}
})
}))
}
if (this.status === 'rejected') {
onrejected(this.reason)
}
if (this.status === 'pending') {
this.onFulfilledArray.push(onfulfilled)
this.onRejectedArray.push(onrejected)
}
}
</code></pre>
<p>
當然,別忘了 this.status'rejected' 狀態和 this.status === 'pending' 狀態也要
加入相同的邏輯,如下。
</p>
<pre><code class="language-js">
Promise.prototype.then = function (onfulfilled, onrejected) {
// promise2將作為then方法的傳回值
let promise2
if (this.status === 'fulfilled') {
return (promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
try {
// 這個新的 promise2 的經過 resolve處理後的值為 onfulfilled 的執行結果
let result = onfulfilled(this.value)
resolve(result)
} catch (e) {
reject(e)
}
})
}))
}
if (this.status === 'rejected') {
return (promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
try {
// 這個新的 promise2 的經過 reject 處理後的值為 onrejected 的執行結果
let result = onrejected(this.value)
resolve(result)
} catch (e) {
reject(e)
}
})
}))
}
if (this.status === 'pending') {
return (promise2 = new Promise((resolve, reject) => {
this.onFulfilledArray.push(() => {
try {
let result = onfulfilled(this.value)
resolve(result)
} catch (e) {
reject(e)
}
})
this.onRejectedArray.push(() => {
try {
let result = onrejected(this.reason)
resolve(result)
} catch (e) {
reject(e)
}
})
}))
}
}
</code></pre>
<p>
這裡要種點了解 this.status === 'pending' 判斷分支中的邏輯,這也是最難了解的。當使用
Promise 實例呼叫其 then 方法時,應該傳回一個 Promise 實例,傳回的就是 this.status ===
'pending' 判斷分支中傳回的 promise2。那麽,這個 promise2
什麼時候被決議呢?應該是在非同步處理結束後,依次執行 onFulfilledArray 或 onRejectedArray
陣列中的函數時。
</p>
<p>
我們再思考一下,onFulfilledArray 或 onRejectedArray
陣列中的函數應該做些什麼呢?很明顯,需要切換 promise2 的狀態,並進行決議。
</p>
<p>
理順了 onFulfilledArray 或 onRejectedArray 陣列中的函數需要執行的邏輯,再進行改動。將
this.onFulfilledArray.push 的函數由 this.onFulfilledArray.push(onfulfilled) 改為以下形式。
</p>
<pre><code class="language-js">
() => {
setTimeout(() => {
try {
let result = onfulfilled(this.value)
resolve(result)
} catch (e) {
reject(e)
}
})
}
</code></pre>
<p>this.onRejectedArray.push 函數的改動方式同理。</p>
<p>
這裡的改動非常不容易了解,如果讀者仍然想不明白,也不需要著急,還是應該先了解透
Promise,再傳回來看,多看幾次,一定會有所收穫。
</p>
<p>請注意,此時 Promise 實現的完整程式如下。</p>
<pre><code class="language-js">
function Promise(executor) {
this.status = 'pending'
this.value = null
this.reason = null
this.onFulfilledArray = []
this.onRejectedArray = []
const resolve = (value) => {
if (value instanceof Promise) {
return value.then(resolve, reject)
}
setTimeout(() => {
if (this.status === 'pending') {
this.value = value
this.status = 'fulfilled'
this.onFulfilledArray.forEach((func) => {
func(value)
})
}
})
}
const reject = (reason) => {
setTimeout(() => {
if (this.status === 'pending') {
this.reason = reason
this.status = 'rejected'
this.onRejectedArray.forEach((func) => {
func(reason)
})
}
})
}
try {
executor(resolve, reject)
} catch (e) {
reject(e)
}
}
Promise.prototype.then = function (onfulfilled, onrejected) {
// promise2將作為then方法的傳回値
let promise
if (this.status === 'fulfilled') {
return (promise = new Promise((resolve, reject) => {
setTimeout(() => {
try {
// 這個新的promise2的經過reject處理後的值為onrejected的執行結果
let result = onfulfilled(this.value)
resolve(result)
} catch (e) {
reject(e)
}
})
}))
}
if (this.status === 'rejected') {
return (promise = new Promise((resolve, reject) => {
setTimeout(() => {
try {
let result = onrejected(this.value)
resolve(result)
} catch (e) {
reject(e)
}
})
}))
}
if (this.status === 'pending') {
return (promise = new Promise((resolve, reject) => {
this.onFulfilledArray.push(() => {
try {
let result = onfulfilled(this.value)
resolve(result)
} catch (e) {
reject(e)
}
})
this.onRejectedArray.push(() => {
try {
let result = onrejected(this.reason)
resolve(result)
} catch (e) {
reject(e)
}
})
}))
}
}
</code></pre>
<h3>鏈式呼叫的增強實現</h3>
<p>我們繼續來實現 then 方法,以便顯性傳回一個 Promise 實例。對應場景下的實現程式如下。</p>
<pre><code class="language-js">
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('lucas')
}, 2000)
})
promise
.then((data) => {
console.log(data)
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(`${data} next then`)
}, 4000)
})
})
.then((data) => {
console.log(data)
})
// lucas
// Promise {<pending>}
// VM549:11 lucas next then
</code></pre>
<p>
基於第一種 onfulfilled 函數和 onrejected 函數傳回一個普通值的情況,要實 現這種 onfulfilled
函數 onrejected 函數傳回一個 Promise 實例的情況並不困
難。但是需要小幅度重構一下程式,在之前實現的 let result = onfulfilled(this.value) 敘述和
let result = onrejected(this reason) 敘述中,使變數 result 由一個普通值變為一個 Promise
實例。換句話說就是,變數 result 既可以是一個普通值,也可以是一個 Promise
實例,為此我們抽象出 resolvePromise 方法進行統 一處理。對已有實現進行改動後的程式如下。
</p>
<pre><code class="language-js">
const resolvePromise = (promise2, result, resolve, reject) => {}
Promise.prototype.then = function (onfulfilled, onrejected) {
// promise2 將作為then方法的傳回值
let promise2
if (this.status === 'fulfilled') {
return (promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
try {
// 這個新的 promise2 的經過 resolve 處理後的值為 onfulfilled 的執行結果
let result = onfulfilled(this.value)
resolvePromise(promise, result, resolve, reject)
} catch (e) {
reject(e)
}