-
Notifications
You must be signed in to change notification settings - Fork 0
/
1-20.html
703 lines (661 loc) · 24.8 KB
/
1-20.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
<!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-20">1-20 常見面試題</h2>
<h3>題意分析</h3>
<p>請實現一個 LazyMan,使其按照以下方式呼叫時,獲得相關輸出。</p>
<pre><code class="language-js">
LazyNan("Hank")
// Hi! This is Hank!
LazyMan("Hank").sleep(10).eat("dinner")
// Hi! This is Hank!
// 等待10s
// Wake up after 10
// Eat dinner~
LazyMan("Hank").eat("dinner").eat("supper")
// Hi This is Hank!
// Eat dinner~
// Eat supper~
LazyMan("Hank").sleepFirst(5).eat("supper")
// 等待 5s
// Wake up after 5
// Hi This is Hank!
// Eat supper
</code></pre>
<h4>分析如下</h4>
<ul>
<li>可以把 LazyMan 了解為一個建構函數,在呼叫時輸出參數內容。</li>
<li>LazyMan 支援鏈式呼叫。</li>
<li>鏈式呼叫過程提供了以下幾個方法:sleepFirst、eat、sleep。</li>
<li>其中,eat 方法會輸出與參數相關的內容:Eat +參數。</li>
<li>
sleep 方法比較特殊,會使鏈式呼叫暫停一定時間後繼續執行,看到這裡也許應該根到
setTimeout。
</li>
<li>
sleepFirst 最為特殊,這個方法的優先順序最高;呼叫 sleepFirst
之後,鏈式呼叫將暫停一定時間後繼續執行。請再次觀察題幹及最後一個 demo,會發現 sleepFirst
的輸出優先順序最高,執行後會使程式先等待Ss再輸出 Walkup after 5,接著輸出 Hi This is
Hank!
</li>
</ul>
<h4>解題思路</h4>
<ul>
<li>首先,可以封裝一些基礎方法,如 log、setTimeout 等。</li>
<li>
因為 LazyMan 要實現一系列呼叫,且呼叫並不是循序執行的,舉例來說,如果 sleepFirst
出現在呼叫鏈中時就被優先執行,而且任務並不全都同步執行,那麼我們應該實現一個任務佇列,這個佇列將排程執行各個任務。
</li>
<li>
因此,每次呼叫 LazyMan
或鏈式即時執行,都應該將相關呼叫方法加入住務佇列並儲存起來,以便後續統一排程。
</li>
<li>
在寫入任務佇列時,如果目前方法為 sleepFirst
,則需要將該方法放到佇列的頭部。寫入任務佇列的方法應該是一個 unshift 方法。
</li>
</ul>
<h4>考點如下</h4>
<ul>
<li>物件導向的思想與設計,包含類別的使用等。</li>
<li>對物件方法鏈式呼叫的了解和設計。</li>
<li>小部分設計模式的設計。</li>
<li>因為存在重複邏輯,所以會考驗到程式的解耦和抽象能力。</li>
<li>邏輯的清晰程度及其他程式設計思維。</li>
</ul>
<h3>解答</h3>
<pre><code class="language-js">
class LazyManGenerator {
constructor (name) {
this.taskArray = []
// 初始化任務
const task=()=>{
console.log( `Hi! This is ${name}`)
// 執行完初始化任務後,繼續執行下一個任務
this.next()
}
// 將初始化任務放入任務佇列中
this.taskArray.push(task)
setTimeout (() => {
this.next()
},
0)
}
next() {
// 取出下一個任務並執行
const task = this.taskArray.shift()
task && task()
}
sleep(time) {
this.sleepTask(time, false)
// return this 保持鏈式呼叫
return this
}
sleepFirst(time) {
this.sleepTask(time, true)
return this
}
sleepTask (time, prior) {
const task=()=>{
setTimeout(()=>{
console.log(`Wake up after ${time}`)
this.next()
}, time * 1000)
}
if (prior) {
this.taskArray.unshift(task)
} else {
this.taskArray.push(task)
}
}
eat (name) {
const task = () => {
console.log(`Eat ${name}`)
this.next()
}
this.taskArray.push(task)
return this
}
}
function LazyMan(name) {
return new LazyManGenerator(name)
}
</code></pre>
<ul>
<li>LazyMan 方法傳回一個 LazyManGenerator 建構函數的實例。</li>
<li>
在 LazyManGenerator 的 constructor 中,使用 taskArray 來儲存任務,同時將初始化任務放入
taskArray 中。
</li>
<li>
還是在 LazyManGenerator 的 constructor 中,將對任務的一個執行操作即 next 呼叫放在
setTimeout 中,這樣就能夠確保在開始執行任務時,taskArray 陣列中已經填滿了任務。
</li>
<li>在 next 方法中,取出 taskArray 陣列中的首項進行執行。</li>
<li>
eat 方法將 eat task 放到 taskArray 陣列中,注意,eat task方法需要呼叫this.next()
顯性呼叫下的任務;同時傳回 this,完成鏈式呼叫。
</li>
<li>
sleep 和 sleepFirst 都呼叫了 sleepTask,只是呼叫 sleepTask 時的第二個參數不同。sleepTask
的第二個參數表示是否優先執行,如果 prior 為 true,則使用 unshift 將任務插入 taskArray
頭部。
</li>
</ul>
<p>
主要的思想是針對過程。解答的關鍵在於對 setTimeout 任務佇列要有準確的了解並掌握 return this
實現鍵式呼叫的方式。
</p>
<p>
事寶上,sleepTask 應該作為 LazyManGenerator 類別的私有屬性出現,本案例到此只是 ES class
的私有屬性暫時沒有被廣泛應用。
</p>
<h3>流程控制和中介軟體</h3>
<p>上面程式中的 next 函數用來負責找出 stack 中的下一個函數並執行。</p>
<p>
Node.js 中的 connect 類別庫,以及其他架構的中介軟體設計也都離不開具有類似思想的 next
函數。舉例來説,產生器自動執行類別庫 co、狀態管理類別庫 redux、架構 Koa 也都有各自 next
函數的實現。我們實際來看一下。
</p>
<h4>Node.js 中的 connect 和 express 類別庫的流程控制</h4>
<p>
實際場景:在 Node.js 環境中,我們已經有 parseBody、checkIdInDatabase
等相關中介軟體,他們組成了 middlewares 陣列。
</p>
<p>透過下面的程式可以明確地看到,middlewares 是含有 3 個中介軟體的陣列。</p>
<pre><code class="language-js">
const middlewares = [
function middleware1(reg, res, next) {
parseBody(reg, function (err, body) {
if (err) return next(err)
reg.body = body
next()
})
},
function middleware2(reg, res, next) {
checkIdInDatabase(reg.body.id, function (err, rows) {
if (err) return next(err)
res.dbResult = rows
next()
})
},
function middleware3(reg, res, next) {
if (res.dbResult && res.dbResult.length > 0) {
res.end('true')
} else {
res.end('false')
}
next()
}
]
</code></pre>
<p>當處理一個請求時,需要鏈式呼叫各個中介軟體,程式如下。</p>
<pre><code class="language-js">
const requestHandler = (reg, res) => {
let i = 0
function next(err) {
if (err) {
return res.end('error:', err.toString())
}
if (i < middlewares.length) {
middlewares[i++](reg, res, next)
} else {
return
}
}
// 初始執行第一個中介軟體
next()
}
</code></pre>
<p>
這個場景所表現的流程控制很簡單,就是將所有中介軟體(任務處理函數)儲存在一個 list
中,然後依次循環呼叫中介軟體(任務處理函數)
</p>
<p>
但是,如何實現得更加優雅呢?connect 這個類別庫對這種實現做了很好的封裝, connect
類別庫的實現方案也為 express 等架構設計實現提供了靈感。這裡簡單分析一下 connect
這個類別庫的實現。
</p>
<p>首先,使用 createServer 方法建立 app實例,如下。</p>
<pre><code class="language-js">
const app = createServer()
</code></pre>
<p>
createServer 方法對應的原始程式如下。其中,app 寶例繼承了 EventEmitter
類別,以便實現事件發佈/訂閱,同時使用 stack 陣列來維護各個中介軟體任務。
</p>
<pre><code class="language-js">
function createServer() {
function app(reg, res, next) {
app.handle(req, res, next)
}
merge(app, proto)
merge(app, EventEmitter.prototype)
app.route = '/'
app.stack = []
return app
}
</code></pre>
<p>接著,使用 app.use 來增加中介軟體,程式如下。</p>
<pre><code class="language-js">
app.use('/api', function(reg,res,next){ //... })
</code></pre>
<p>use 方法的原始程式如下。</p>
<pre><code class="language-js">
proto.use = function use(route, fn) {
var handle = fn
var path = route
// default route to '/'
if (typeof route !== 'string') {
handle = route
path = '/'
}
// wrap sub-apps
if (typeof handle.handle === 'function') {
var server = handle
server.route = path
handle = function (reg, res, next) {
server.handle(reg, res, next)
}
}
// wrap vanilla http.Servers
if (handle instanceof http.Server) {
handle = handle.listeners('request')[0]
}
// strip trailing slash
if (path[path.length - 1] === '/') {
path = path.slice(0, -1)
}
// add the middleware
debug('use %s %s', path || '/', handle.name || 'anonymous')
this.stack.push({ route: path, handle: handle })
return this
}
</code></pre>
<p>上述程式示範了 use 函數的實現方法,選輯並不複雜,下面來簡單分析一下。</p>
<p>程式中透過 if...else 選輯區分出以下3種不同的和類型。</p>
<ul>
<li>fn 是一個普通的 function(req,res[,next]){} 函數。</li>
<li>fn 是一個普通的 httpServer。</li>
<li>fn 是另一個 connect 的 app 物件。</li>
</ul>
<p>完成了中介軟體註冊,再來看一看任務的排程和執行,使用方法如下。</p>
<pre><code class="language-js">
app.handle(reg, res, out)
</code></pre>
<p>handle 方法對應的原始程式如下</p>
<pre><code class="language-js">
proto.handle = function handle(reg, res, out) {
var index = 0
var protohost = getProtohost(req.url) || ''
var removed = ''
var slashAdded = false
var stack = this.stack
// final function handler
var done =
out ||
finalhandler(reg, res, {
env: env,
onerror: logerror
})
// store the original URL
req.originalUrl = req.originalUrl || req.url
function next(err) {
// ...
}
next()
}
</code></pre>
<p>handle 方法用來建置 next 函數,並觸發執行第一個 next 。</p>
<p>next 的原始程式如下。</p>
<pre><code class="language-js">
function next (err) {
if (slashAdded) {
reg. url = reg.url.substr(1) ;
slashAdded = false;
}
if (removed.length !== 0) {
reg.url = protohost + removed + reg.url.substr(protohost.length) ;
removed = '';
}
// next callback
var layer = stack[index++];
// all done
if (!layer) {
defer(done, err) ;
return;
}
// route data
var path = parseUrl(reg).pathname || '/' ;
var route = layer.route;
// skip this layer if the route doesn't match
if (path.toLowerCase().substr(0, route. length)!== route.toLowerCase()) {
return next(err);
}
// skip if route match does not border "/", ".", or end
var c = path.length > route.length && path[route.length];
if (c && c !== '/' && c!== '.') {
return next (err);
}
// trim off the part of the url that matches the route
if (route.length!== 0 & route !== '/') {
removed = route;
reg.url = protohost + req.url.substr(protohost.length + removed.length) ;
// ensure leading slash
if ( protohost && req.url[0] !== '/') {
reg. url = '/' + reg.url;
slashAdded = true;
}
}
// call the Layer handle
call(layer.handle, route, err, reg, res, next);
}
</code></pre>
<p>next 的實現非常巧妙,下面就來分析一下它。</p>
<ul>
<li>首先,取出下一個中介軟體,程式如下。</li>
</ul>
<pre><code class="language-js">
var layer = stack[index++]
</code></pre>
<ul>
<li>對於目前中介軟體的處理,如果目前的請求路由和 handler 不符合,則跳過,程式如下。</li>
</ul>
<pre><code class="language-js">
if (path.toLowerCase().substr(0, route.length) !== route.toLowerCase()) {
return next(err);
}
</code></pre>
<ul>
<li>若比對,則執行 call 函數,call 函數的實現如下。</li>
</ul>
<pre><code class="language-js">
function call(handle, route, err, req, res, next) {
var arity = handle.length
var error = err
var hasError = Boolean(err)
debug('%s %s : %s', handle.name || '<anonymous>', route, reg.originalUrl)
try {
if (hasError & (arity === 4)) {
// error-handling middleware
handle(err, reg, res, next)
return
} else if (!hasError && arity < 4) {
// request-handling middleware handle (reg, res, next) ;
return
}
} catch (e) {
// replace the error
error
} ;
// continue
next(error)
}
</code></pre>
<p>
注意,這裡使用了 try..catch
語法包裹邏輯,這是很有必要的容錯操作,可以使應用在協力廠商中介軟體執行出錯的情況下不至於當機退出。
</p>
<p>
較為巧妙的一點是,這裡將 function(err, req, res, next) {} 作為錯誤處理函數,而將
function(req, res, next) {} 作為正常的業務邏輯處理函數。因此,可以透過 function.length
判斷目前 handler 是否為容錯函數後,再向 handler 中傳入對應的參數。
</p>
<p>
call 函數是 next 函數的核心,它是一個執行者,可以在最後的邏輯中繼續執行 next
函數,完成中介軟體的順序呼叫。
</p>
<p>
Node.js 的架構 express 實際上就是 senchalabs connect 的升級版,透過對 connect
原始程式的學習,對流程的排程和控制更加清楚了,此時再去看 express 就能輕而易舉地了解了。
</p>
<p>
senchalabs connect 用流程控制函數庫的回呼函數及中介軟體的思想來解耦回呼邏輯,Koa 則是用
Generator 方法解決回呼問題(最新版使用 async/await),事實上,也可以用事件、Promise
的方式來解決。下一節就來分析 Koa 的洋蔥模型。
</p>
<h4>Koa 的洋蔥模型</h4>
<p>針對切面程式設計(AOP)。下面以 JavaScript 語言為例,來看一個簡單的範例。</p>
<pre><code class="language-js">
Function.prototype.before = function (fn) {
const self = this
return function (...args) {
console.log('')
let res = fn.call(this)
if (res) {
self.apply(this, args)
}
}
}
Function.prototype.after = function (fn) {
const self = this
return function (...args) {
let res = self.apply(this, args)
if (res) {
fn.call(this)
}
}
}
</code></pre>
<p>
以上程式在執行某個函數 fn
之前,會先執行某段邏輯,而在執行某個函數血之後,再去執行另一段邏輯。這其實表現了一種簡單的中介軟體流程控制。
不過,這樣的AOP 有一個問題,就是無法實現非同步模式。
</p>
<p>
那麼,如何實現 Koa
的非同步中介軟體模式呢?也就是使某個中介軟體執行到一半時交出執行權,之後再回來繼續執行。下面直接來看一下原始程式,這段原始程式實現了
Koa 洋蔥模型中介軟體。
</p>
<pre><code class="language-js">
function compose (middleware) {
if (!Array.isArray(middleware)) throw new TypeError ('Middleware stack must be an array! ')
for (const fn of middleware) {
if (typeof fn!== 'function') throw new TypeError ('Middleware must be composed of functions!')
}
return function (context, next) {
// last called middleware #
let index = -1
// 一開始的時候傳入為 0,後續會遞增
return dispatch(0)
function dispatch (i) {
// 假如沒有遞增,則說明執行了多次
if (i <= index) return Promise.reject(new Error('next() called multiple times'))
index = i
// 拿到當前的中間件
let fn = middleware[i]
if (i === middleware.length) fn = next
// 當 fn 為空的時候,就會開始執行 next() 後面部分的代碼
if (!fn) return Promise.resolve()
try {
// 執行中間件,留意這兩個參數,都是中間件的傳參,第一個是上下文,第二個是 next 函數
// 也就是說執行 next 的時候也就是調用 dispatch 函數的時候
return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
} catch (err) {
return Promise.reject(err)
}
}
}
}
</code></pre>
<p>下面重點解讀一下以上程式中的要點。</p>
<ul>
<li>傳入 compose 中的 middleware 參數必須是陣列,否則會拋出錯誤。</li>
<li>middleware 陣列中的每個元素都必須是函數,否則會拋出錯誤。</li>
<li>compose 傳回一個函數,以儲存對 middleware 的參考。</li>
<li>
compose 傳回函數的第一個參數是 context,所有中介軟體的第一個參數就是傳入的 context。
</li>
<li>compose 傳回函數的第二個參數是 next 函數 next 是實現洋蔥模型的關鍵。</li>
<li>index 用來記錄目前執行到第幾個中介軟體。</li>
<li>執行第一個中介軟體函數:return dispatch(0)。</li>
<li>
在 dispatch 函數中,參數 i 如果小於等於index,則說明一個中介軟體中執行了多次
next,會進行顯示出錯,由此可見,一個中介軟體函數內部不允許多次呼叫 next 函數。
</li>
<li>取出中介軟體函數 fn = middleware[i] 。</li>
<li>如果 i === middleware.length,則說明執行到了圓心,可以將 next 設定值給 fn。</li>
<li>
因為 async函數中 await 運算式右邊的值一般是 Promise 類型的,所以這裡會包裹一層 Promise。
</li>
<li>next 函數是固定的,它可以執行下一個中介軟體函數。</li>
</ul>
<h4>co 函數庫</h4>
<p>
談到流提控制,自然少不了大名鼎票的 co 函數庫。co 函數庫是以 ES6 Generator
為基礎撰寫的非同步解決方案,因此這裡需要熟練掌握 ES6 Generator 。目前,雖然 co
函數庫目前可能不再流行,但是了解其實現對於模擬類 似場景是非常有必要的。
</p>
<p>這裡不解讀其原始程式,而是實現一個類似的自動執行 Generator 的方案。</p>
<pre><code class="language-js">
const runGenerator = (generatorFunc) => {
const it = generatorFunc()
iterate(it)
function iterate(it) {
step()
function step(arg, isError) {
const { value, done } = isError ? it.throw(arg) : it.next(arg)
let response
if (!done) {
if (typeof value === 'function') {
response = value()
} else {
response = value
}
Promise.resolve(response).then(step, (err = step(err, true)))
}
}
}
}
</code></pre>
<p>下面來重點解讀一下以上程式中的要點。</p>
<ul>
<li>runGenerator 函數接收一個產生器函數 generatorFunc。</li>
<li>執行 generatorFunc 獲得結果,並透過 iterate 函數疊代該產生器結果。</li>
<li>
在 iterate 函數中執行 step 函數, step 函數的第一個參數 arg 是上一個 yield
右運算式求出的值,即下面對應的 response。
</li>
<li>
這裡需要考慮 response 的求值過程,它透過 value 計算得來,value 是 yield
右側的值,它有下面幾種情況。
<ul>
<li>
yield new Promise(), value 是一個 Promise 實例,此時 response 就是該 Promise
實例執行 resolve 後的值。
</li>
<li>
yield () => {return value} , value 是一個函數,此時 response
就是執行該函數後的傳回值。
</li>
<li>yield value , value 是一個普通值,此時 response 就是該值。</li>
</ul>
</li>
<li>
最後統一利用 Promise.resolve 的特性對 response 進行處理,並遞迴(疊代)呼叫
Step,同時利用 Step 函數的 arg 參數為上一個 yield 的左運算式設定值,並傳回下一個 yield
右運算式的值。
</li>
</ul>
<p>最後附上 co 的實現及程式註釋,讀者可以比較 runGenerator 和 co 的差異。</p>
<pre><code class="language-js">
// https://github.com/tj/co/blob/master/index.js
function co(gen) { // co接收一個 Generator 函數作為參數
var ctx = this;
var args = slice.call(arguments, 1);
// we wrap everything in a promise to avoid promise chaining,
// which leads to memory leak errors.
// see https://github.com/tj/co/issues/180
return new Promise(function(resolve, reject) {
if (typeof gen === 'function') gen = gen.apply(ctx, args);
// 若 gen 為 Generator 函數,則執行該函數
if (!gen || typeof gen.next !== 'function') return resolve(gen);
// // 若 gen 不是 Generator 函數,則傳回並更新 Promise 狀態
onFulfilled(); // 將 Generator 函數的 next 方法包裝成 onFulfilled,主要是為了能夠捕捉拋出的異常
/**
* @param {Mixed} res
* @return {Promise}
* @api private
*/
function onFulfilled(res) {
var ret;
try {
ret = gen.next(res);
} catch (e) {
return reject(e);
}
next(ret);
return null;
}
/**
* @param {Error} err
* @return {Promise}
* @api private
*/
function onRejected(err) {
var ret;
try {
ret = gen.throw(err);
} catch (e) {
return reject(e);
}
next(ret);
}
/**
* Get the next value in the generator,
* return a promise.
*
* @param {Object} ret
* @return {Promise}
* @api private
*/
function next(ret) {
if (ret.done) return resolve(ret.value);
var value = toPromise.call(ctx, ret.value);
if (value && isPromise(value)) return value.then(onFulfilled, onRejected);
return onRejected(new TypeError('You may only yield a function, promise, generator, array, or object, '
+ 'but the following object was passed: "' + String(ret.value) + '"'));
}
});
}
</code></pre>
<p>
若對上述程式碼感到陌生,不妨回去重新熟悉 generator 特性:
<a href="/1-2-5.html">1-2-5 實作非同步流程</a>
</p>
</article>
</main>
</body>
<script type="module" src="./js/main.js"></script>
</html>