-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy path21_skillsharing.html
More file actions
639 lines (472 loc) · 103 KB
/
21_skillsharing.html
File metadata and controls
639 lines (472 loc) · 103 KB
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
<!doctype html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>پروژه: وبسایت اشتراک مهارتها :: Eloquent JavaScript</title>
<link rel=stylesheet href="js/node_modules/codemirror/lib/codemirror.css">
<script src="js/acorn_codemirror.js"></script>
<link rel=stylesheet href="css/ejs.css">
<script src="js/sandbox.js"></script>
<script src="js/ejs.js"></script><script>var chapNum = 21;</script><script>var clicky_site_ids = clicky_site_ids || []; clicky_site_ids.push(101171577);</script>
<script async src="//static.getclicky.com/js"></script>
</head>
<article>
<nav><a href="20_node.html" title="previous chapter">◀</a> <a href="index.html" title="cover">◆</a></nav>
<h1><span class=chap_num>فصل 21</span>پروژه: وبسایت اشتراک مهارتها</h1>
<blockquote>
<p><a class="p_ident" id="p_2jmj7l5rSw" href="#p_2jmj7l5rSw" tabindex="-1" role="presentation"></a>اگر دانشی دارید، بگذارید دیگران از چراغ دانشتان بهرهمند شوند.</p>
<footer>مارگارت فولر</footer>
</blockquote><figure class="chapter framed"><img src="img/chapter_picture_21.jpg" alt="Picture of two unicycles"></figure>
<p><a class="p_ident" id="p_Kzh6PibMf8" href="#p_Kzh6PibMf8" tabindex="-1" role="presentation"></a>یک جلسهی اشتراک مهارت، رخدادی است که در آنجا افرادی با یک علاقهی مشترک دور هم جمع میشوند و دانش خود را به صورت خودمانی و کوتاه ارائه میکنند. در یک جلسهی اشتراک مهارت باغبانی، ممکن است فردی به توضیح نحوهی کاشت کرفس بپردازد. یا در یک گروه اشتراک مهارت برنامهنویسی، شما میتوانید شرکت کنید و دربارهی Node.js مطلبی ارائه کنید.</p>
<p><a class="p_ident" id="p_VblVcbcvBc" href="#p_VblVcbcvBc" tabindex="-1" role="presentation"></a>اینگونه جلسات- که اغلب وقتی دربارهی کامپیوتر است، گروههای کاربران نامیده میشود (users’ groups)- روش مناسبی برای وسعت بخشیدن به گسترهی دانشتان، یادگیری دربارهی پیشرفتها و توسعههای جدید، یا ملاقات افراد جدید با علايق مشترک میباشند. خیلی از شهرهای بزرگ جلسات جاوااسکریپت دارند. معمولا شرکت در اینگونه جلسات رایگان است، و جلساتی که من تجربه کردهام، همگی گرم و دوستانه بوده اند.</p>
<p>در این فصل پروژهی پایانی، هدف ما این است که وبسایتی برای مدیریت ارائههایی که در یک جلسهی اشتراک مهارت اجرا میشود، ایجاد کنیم. تصور کنید که گروه کوچکی از کاربران به صورت منظم در دفتر کار یکی از اعضا ملاقات میکنند تا دربارهی یکچرخهها صحبت کنند. مسئول برگزاری پیشین به شهر دیگری نقل مکان کرده است و کسی برای قبول این کار قدم پیش نگذاشته است. ما به سیستمی نیاز داریم که به شرکت کنندگان این امکان را بدهد تا بتوانند ارائهها را پیشنهاد داده و در مورد آنها بحث کنند بدون اینکه نیازی به یک مدیر برگزارکنندهی مرکزی باشد.</p>
<p><a class="p_ident" id="p_eU1l6KAqwe" href="#p_eU1l6KAqwe" tabindex="-1" role="presentation"></a>درست مانند <a href="20_node.html">فصل پیش</a>، بخشی از کدی که در این فصل میآید برای محیط Node.js نوشته شده است، و اجرای مستقیم آن در صفحهی HTMLای کد را در آن مشاهده میکنید، بعید است که کار کند. کد کامل پروژه را میتوانید از <a href="https://eloquentjavascript.net/code/skillsharing.zip"><em>https://eloquentjavascript.net/code/skillsharing.zip</em></a> دانلود کنید.</p>
<h2><a class="h_ident" id="h_RE4rvQdTSr" href="#h_RE4rvQdTSr" tabindex="-1" role="presentation"></a>طراحی</h2>
<p>در این پروژه، بخشی مربوط به سرویسدهنده است که برای Node.js نوشته شده است، و بخشی مربوط به کلاینت که برای مرورگر نوشته شده است. سرویسدهنده دادههای سیستم را ذخیره می کند و آنها را برای کلاینت فراهم میسازد. همچنین میزبانی و سرو فایلهایی که بخش سمت کلایت را پیاده سازی میکنند نیز با سرویس دهنده است.</p>
<p><a class="p_ident" id="p_9Asn1ri5pP" href="#p_9Asn1ri5pP" tabindex="-1" role="presentation"></a>سرویسدهنده لیست ارائههایی که برای جلسهی بعدی پیشنهاد شده اند را نگهداری میکند و کلاینت آنها را نمایش میدهد. هر ارائه دارای یک نام ارائه کننده، یک عنوان، یک خلاصه، و آرایهای از نظرات مرتبط با آن میباشد. کلاینت به کاربران امکان پیشنهاد ارائههای جدید ( اضافه کردن آنها به لیست)، حذف آنها و ارسال نظر به ارائههای فعلی را فراهم میسازد. هر بار که کاربر تغییری اینگونه ایجاد میکند، کلاینت یک درخواست HTTP به سرویسدهنده برای آن ارسال می کند.</p><figure><img src="img/skillsharing.png" alt="Screenshot of the skill-sharing website"></figure>
<p>اپلیکیشن به صورتی تنظیم خواهد شد که یک نمای زنده از ارائههای پیشنهاد شدهی کنونی نمایش دهد. هنگامی که کسی، جایی، یک ارائهی جدید ثبت میکند یا نظری ارسال میکند، همهی افرادی که صفحهی سایت را باز نگهداشته اند بایستی تغییرات را بلافاصله ببینند. این ویژگی کمی چالش ایجاد خواهد کرد- زیرا راهی وجود ندارد که سرویسدهنده تماسی را به یک کلاینت برقرار سازد، و همچنین راه مناسبی برای دانستن اینکه کدام کلاینتها در حال حاضر در حال مشاهدهی یک وب سایت هستند وجود ندارد.</p>
<p><a class="p_ident" id="p_moO5rdLUpJ" href="#p_moO5rdLUpJ" tabindex="-1" role="presentation"></a>یک راه حل رایج برای این مشکل وجود دارد که <em>long polling</em> نامیده میشود که یکی از انگیزههای موجود برای طراحی Node بوده است.</p>
<h2><a class="h_ident" id="h_Yxu7U155Cs" href="#h_Yxu7U155Cs" tabindex="-1" role="presentation"></a>Long polling</h2>
<p>برای اینکه بتوان بلافاصله کلاینت را از تغییری باخبر کرد، لازم است تا ارتباطی با کلاینت برقرار کنیم. با توجه به اینکه مرورگرهای وب از دیرباز درخواست اتصال را قبول نمیکنند و اغلب پشت روترهایی هستند که اینگونه درخواستهای اتصال را بلاک میکنند، ارسال اتصال توسط سرویسدهنده کارایی ندارد.</p>
<p>می توانیم کلاینت را طوری هماهنگ کنیم که اتصالی برقرار کرده و آن را باز نگهدارد تا سرویسدهنده بتواند با کمک آن اطلاعاتی که نیاز است ارسال شود را ارسال کند.</p>
<p><a class="p_ident" id="p_OttQZ2tcja" href="#p_OttQZ2tcja" tabindex="-1" role="presentation"></a>اما درخواستهای HTTP فقط از جریانهای سادهی داده پشتیبانی می کنند: کلاینت یک درخواست ارسال مینماید، سرویسدهنده یک پاسخ برای آن درخواست برمیگرداند، و فقط همین. فناوریای وجود دارد که WebSockets نام دارد، و توسط مرورگرهای مدرن پشتیبانی میشود. به کمک این فناوری، میتوان اتصالاتی برای تبادل دادهها به صورت دلخواه باز نمود. اما استفادهی درست از آن کمی پیچیده است.</p>
<p><a class="p_ident" id="p_eGaCF2UbPz" href="#p_eGaCF2UbPz" tabindex="-1" role="presentation"></a>در این فصل، ما از تکنیکی ساده تر-long polling- استفاده میکنیم جایی که کلاینتها به صورت مداوم از سرویسدهنده به وسیلهی درخواستهای HTTP معمولی، تقاضای دادههای جدید میکند، و سرویسدهنده در صورت نبود چیز جدیدی برای گزارش، ارسال پاسخ را متوقف می کند.</p>
<p><a class="p_ident" id="p_k+Z0MmcQlK" href="#p_k+Z0MmcQlK" tabindex="-1" role="presentation"></a>اگر کلاینت همیشه درخواست بازی از نوع polling داشته باشد، میتواند انتظار داشته باشد که در صورت در دسترس قرار گرفتن دادهی جدید، آن را به سرعت از سرویسدهنده دریافت خواهد کرد. به عنوان مثال، اگر Fatma سایت اشتراک مهارت ما را مرورگرش باز داشته باشد، آن مرورگر درخواستی برای بهروزرسانیها به سرویسدهنده خواهد داشت و منتظر پاسخ برای آن میماند. زمانی که Iman ارائه ای را در مورد یکچرخهسواری در شیبهای تند ثبت میکند، سرویسدهنده میداند که Fatma منتظر خبر جدیدی است و پاسخی حاوی ارائه جدید ثبت شده به درخواست او ارسال میکند. مرورگر Fatma دادهها را دریافت کرده و صفحهی نمایش را با اطلاعات ارائهی جدید بهروز میکند.</p>
<p><a class="p_ident" id="p_R/2ZPvNob1" href="#p_R/2ZPvNob1" tabindex="-1" role="presentation"></a>برای جلوگیری از لغو شدن اتصالها به دلیل نبود فعالیت، تکنیکهای long polling معمولا یک بیشینهی زمان برای هر درخواست در نظر میگیرند، پس از گذشت آن زمان، سرویسدهنده پاسخی را به هر حال ارسال میکند، حتی درصورتی که چیزی برای گزارش نداشته باشد، که بعد از آن کلاینت دوباره درخواستی ارسال میکند. دوباره ارسال مدوام درخواست باعث میشود که تکنیک پایدار شود، زیرا به کلاینت امکان پوشش مشکلات موقت سرویسدهنده و قطع ارتباط میدهد.</p>
<p><a class="p_ident" id="p_OWfPCL60OH" href="#p_OWfPCL60OH" tabindex="-1" role="presentation"></a>یک سرویسدهندهی پرترافیک که از تکنیک long polling استفاده میکند ممکن است هزاران درخواست منتظر پاسخ داشته باشد، که به معنای همین تعداد اتصال TCP باز میباشد. Node، که امکان مدیریت اتصالهای زیاد بدون نیاز به ایجاد threadهای مجزا و کنترل آنها را به آسانی فراهم میسازد، گزینهی مناسبی برای اینگونه سیستمها میباشد.</p>
<h2><a class="h_ident" id="h_egH5n1oXU2" href="#h_egH5n1oXU2" tabindex="-1" role="presentation"></a>رابط HTTP</h2>
<p>پیش از اینکه به سراغ طراحی سرویسدهنده یا کلاینت برویم، اجازه دهید به نقطهای که هر دوی آنها با آن ارتباط برقرار میکنند فکر کنیم: رابط HTTP که در بستر آن تعامل صورت خواهد گرفت.</p>
<p><a class="p_ident" id="p_X3ZCtxEtRW" href="#p_X3ZCtxEtRW" tabindex="-1" role="presentation"></a>ما از JSON به عنوان فرمت بدنهی درخواستها و پاسخهایمان استفاده خواهیم کرد. درست مانند سرویسدهندهی فایل مربوط به <a href="20_node.html#file_server">Chapter 20</a>، سعی خواهیم کرد که از متدهای HTTP و سرنامها به درستی بهره ببریم. رابط ما پیرامون مسیر <bdo><code>/talks</code></bdo> خواهد بود. مسیرهایی که با <bdo><code>/talks</code></bdo> شروع نمیشوند برای سرو فایلهای ایستا-کدهای جاوااسکریپت و HTML مربوط به سیستم کلاینت - استفاده میشوند.</p>
<p><a class="p_ident" id="p_AxpOdvCznQ" href="#p_AxpOdvCznQ" tabindex="-1" role="presentation"></a>یک درخواست <code>GET</code> به مسیر <bdo><code>/talks</code></bdo>، یک سند JSON به صورت زیر برمیگرداند:</p>
<pre class="snippet cm-s-default" data-language="application/json" ><a class="c_ident" id="c_97pdSGxLAU" href="#c_97pdSGxLAU" tabindex="-1" role="presentation"></a>[{<span class="cm-string cm-property">"title"</span>: <span class="cm-string">"Unituning"</span>,
<span class="cm-string cm-property">"presenter"</span>: <span class="cm-string">"Jamal"</span>,
<span class="cm-string cm-property">"summary"</span>: <span class="cm-string">"Modifying your cycle for extra style"</span>,
<span class="cm-string cm-property">"comments"</span>: []}]}</pre>
<p>ایجاد یک ارائهی جدید به وسیلهی یک درخواست <code>PUT</code> به آدرسی مانند <bdo><code>/talks/Unituning</code></bdo> صورت میگیرد، جاییکه بخش بعد از اولین اسلش نمایانگر عنوان ارائه میباشد. بدنهی درخواست <code>PUT</code> باید حاوی یک شیء JSON باشد که که دارای خاصیتهای <code>presenter</code> و <code>summary</code> باشد.</p>
<p><a class="p_ident" id="p_btPjMuvc3c" href="#p_btPjMuvc3c" tabindex="-1" role="presentation"></a>با توجه به اینکه عنوان ارائهها ممکن است دارای فاصله یا دیگر کاراکترهایی باشد که ممکن است در URL درست نمایش نیابند، عنوانها باید به وسیلهی <code>encodeURIComponent</code> در هنگام ساخت URL کدگذاری شوند.</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_vixRLAECCO" href="#c_vixRLAECCO" tabindex="-1" role="presentation"></a><span class="cm-variable">console</span>.<span class="cm-property">log</span>(<span class="cm-string">"/talks/"</span> <span class="cm-operator">+</span> <span class="cm-variable">encodeURIComponent</span>(<span class="cm-string">"How to Idle"</span>));
<span class="cm-comment">// → /talks/How%20to%20Idle</span></pre>
<p>یک درخواست برای ایجاد ارائهای دربارهی حرکت بدون رکابزدن ممکن است چیزی شبیه درخواست زیر باشد:</p>
<pre class="snippet cm-s-default" data-language="http" ><a class="c_ident" id="c_G609VIyy0C" href="#c_G609VIyy0C" tabindex="-1" role="presentation"></a>PUT /talks/How%20to%20Idle HTTP/1.1
Content-Type: application/json
Content-Length: 92
{"presenter": "Maureen",
"summary": "Standing still on a unicycle"}</pre>
<p><a class="p_ident" id="p_jppeVy+LYT" href="#p_jppeVy+LYT" tabindex="-1" role="presentation"></a>همچنین اینگونه URLها درخواستهای <code>GET</code> برای دریافت نمایش JSON یک ارائه و درخواست <code>DELETE</code> برای حذف یک ارائه را پشتیبانی میکند.</p>
<p><a class="p_ident" id="p_kBY8Yl9XD/" href="#p_kBY8Yl9XD/" tabindex="-1" role="presentation"></a>افزودن یک نظر (comment) به یک ارائه به وسیلهی یک درخواست <code>POST</code> به یک URL شبیه به <bdo><code>/<wbr>talks/<wbr>Unituning/<wbr>comments</code></bdo> صورت میگیرد که بدنهی درخواست به صورت JSON و دارای خاصیتهای <code>message</code> و <code>author</code> میباشد.</p>
<pre class="snippet cm-s-default" data-language="http" ><a class="c_ident" id="c_JUbwWgr3xI" href="#c_JUbwWgr3xI" tabindex="-1" role="presentation"></a>POST /talks/Unituning/comments HTTP/1.1
Content-Type: application/json
Content-Length: 72
{"author": "Iman",
"message": "Will you talk about raising a cycle?"}</pre>
<p>برای پشتیبانی از تکنیک long polling، درخواستهای <code>GET</code> به <bdo><code>/talks</code></bdo> باید سرنامهای بیشتری داشته باشند تا به سرویسدهنده خبر دهند که در صورت نبود خبر جدید، ارسال پاسخ را باید به تاخیر بیاندازد. ما از یک جفت سرنام که معمولا برای مدیریت کش(حافظهی نهان) استفاده میشوند، <code>ETag</code> و <code>If-None-Match</code> استفاده میکنیم.</p>
<p><a class="p_ident" id="p_eYFuywp14L" href="#p_eYFuywp14L" tabindex="-1" role="presentation"></a>سرویسدهندهها ممکن است یک سرنام <code>Etag</code> (برچسب موجودیت) در یک پاسخ قرار دهند. مقدار آن رشتهای است که نسخهی فعلی منبع را مشخص میکند. کلاینتها، وقتی در آینده آن منبع را درخواست میکنند، ممکن است یک درخواست شرطی بسازند که این کار را با قرار دادن سرنام <code>If-None-Match</code> و مقداری مشابه مقدار <code>Etag</code> در درخواست انجام میدهند. اگر منبع مورد نظر تغییر نکرده است، سرویسدهنده پاسخی با کد وضعیت 304 ارسال میکند که معنای آن "تغییر نکرده است" میباشد. این پاسخ به کلاینت میگوید که نسخهی کش شده مشابه نسخهی فعلی است. زمانی که مقدار برچسب متفاوت بود، سرویسدهنده به صورت عادی پاسخ میدهد.</p>
<p><a class="p_ident" id="p_34/jwY39Xy" href="#p_34/jwY39Xy" tabindex="-1" role="presentation"></a>ما به چیزی مثل این نیاز داریم، کلاینت بتواند به سرویسدهنده بگوید که کدام نسخه از لیست ارائهها را دارد، و سرویسدهنده فقط زمانی پاسخ دهد که آن لیست بهروز شده است. اما بهجای اینکه بلافاصله پاسخی با کد 304 برگرداند، سرویسدهنده باید پاسخ را نگهدارد و فقط زمانی پاسخ دهد که چیزی تغییر کرده یا زمان مشخصی طی شده است. برای ایجاد تمایز بین درخواستهای long polling و درخواستهای معمولی، سرنام دیگری، <code>Prefer: wait=90</code>، اضافه میکنیم که به سرویسدهنده میگوید کلاینت تا 90 ثانیه میتواند برای پاسخ صبر کند.</p>
<p>سرویسدهنده یک شماره نسخه که با هر بار تغییر ارائهها بهروز میشود را نگهداری کرده و آن را به عنوان مقدار <code>ETag</code> استفاده میکند. کلاینتها میتوانند درخواستهایی مانند زیر را ارسال کنند تا با بروز یک تغییر از آن باخبر شوند:</p>
<pre class="snippet cm-s-default" data-language="null" ><a class="c_ident" id="c_RikeEeOO6T" href="#c_RikeEeOO6T" tabindex="-1" role="presentation"></a>GET /talks HTTP/1.1
If-None-Match: "4"
Prefer: wait=90
(time passes)
HTTP/1.1 200 OK
Content-Type: application/json
ETag: "5"
Content-Length: 295
[....]</pre>
<p>پروتکلی که اینجا شرح داده شد هیچ کنترل درخواستی را انجام نمیدهد. هر کسی میتواند نظر دهد، ارائهها را تغییر دهد، و حتی حذفشان کند. (با توجه به اینکه اینترنت پر است از افراد خرابکار، اگر سیستمی اینچنینی را جایی در اینترنت بدون لایهای محافظ قرار دهید، نباید انتظار پایان خوشی داشته باشید. )</p>
<h2><a class="h_ident" id="h_Po9VtAncKj" href="#h_Po9VtAncKj" tabindex="-1" role="presentation"></a>سرویسدهنده</h2>
<p><a class="p_ident" id="p_Jg96jNT2k4" href="#p_Jg96jNT2k4" tabindex="-1" role="presentation"></a>خب بیایید ساخت بخش مربوط به سرویسدهنده را شروع کنیم. کد این بخش در محیط Node اجرا میشود.</p>
<h3><a class="i_ident" id="i_upU9jyWr6t" href="#i_upU9jyWr6t" tabindex="-1" role="presentation"></a>Routing (مسیرگزینی)</h3>
<p>سرویسدهندهی ما از <code>createServer</code> برای شروع یک سرویسدهندهی HTTP استفاده میکند. در تابعی که قرار است درخواستهای جدید را مدیریت کند، باید بین انواع درخواستهایی که ما پشتیبانی میکنیم تمایز قائل شویم (با توجه به متد درخواست و مسیر درخواستی). این کار را میتواند به وسیلهی یک زنجیرهی بلند از دستورات <code>if</code> انجام داد، اما راه نیکوتری وجود دارد.</p>
<p><a class="p_ident" id="p_FpAwwHnsgW" href="#p_FpAwwHnsgW" tabindex="-1" role="presentation"></a>یک <em>router</em> یک مؤلفه است که به ما کمک میکند تا یک درخواست را به تابعی که میتواند آن را رسیدگی کند گسیل دهیم. میتوانید برای مسیرگزین(router) مشخص کنید که مثلا درخواستهای <code>PUT</code> که دارای مسیری باشند که با عبارت باقاعدهی <bdo><code>/<wbr>^\/<wbr>talks\/<wbr>([^\/<wbr>]+)$/<wbr></code></bdo> تطابق داشته باشد (<bdo><code>/talks/</code></bdo> و پس از آن یک عنوان) میتوانند با یک تابع داده شده رسیدگی شوند. افزون بر آن، مسیرگزین میتواند برای استخراج بخشهای معنادار مسیر (در این مورد عنوان ارائه) استفاده شود بخشی که در عبارت باقاعده درون پرانتز قرار دارد و نتیجه را به تابع رسیدگیکننده ارسال کند.</p>
<p><a class="p_ident" id="p_4kP9E2MClg" href="#p_4kP9E2MClg" tabindex="-1" role="presentation"></a>در NPM چندین بستهی خوب برای مدیریت مسیرها (routing) وجود دارد، اما ما اینجا نسخهی خودمان را مینویسیم تا قواعد آن را روشن کنیم.</p>
<p>فایلی به نام <code>router.js</code> وجود دارد که در ادامه از ماژول سرویسدهندهی ما <code>require</code> خواهد شد:</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_0cNWrBAXST" href="#c_0cNWrBAXST" tabindex="-1" role="presentation"></a><span class="cm-keyword">const</span> {<span class="cm-def">parse</span>} <span class="cm-operator">=</span> <span class="cm-variable">require</span>(<span class="cm-string">"url"</span>);
<span class="cm-variable">module</span>.<span class="cm-property">exports</span> <span class="cm-operator">=</span> <span class="cm-keyword">class</span> <span class="cm-def">Router</span> {
<span class="cm-property">constructor</span>() {
<span class="cm-keyword">this</span>.<span class="cm-property">routes</span> <span class="cm-operator">=</span> [];
}
<span class="cm-property">add</span>(<span class="cm-def">method</span>, <span class="cm-def">url</span>, <span class="cm-def">handler</span>) {
<span class="cm-keyword">this</span>.<span class="cm-property">routes</span>.<span class="cm-property">push</span>({<span class="cm-property">method</span>, <span class="cm-property">url</span>, <span class="cm-property">handler</span>});
}
<span class="cm-property">resolve</span>(<span class="cm-def">context</span>, <span class="cm-def">request</span>) {
<span class="cm-keyword">let</span> <span class="cm-def">path</span> <span class="cm-operator">=</span> <span class="cm-variable">parse</span>(<span class="cm-variable-2">request</span>.<span class="cm-property">url</span>).<span class="cm-property">pathname</span>;
<span class="cm-keyword">for</span> (<span class="cm-keyword">let</span> {<span class="cm-def">method</span>, <span class="cm-def">url</span>, <span class="cm-def">handler</span>} <span class="cm-keyword">of</span> <span class="cm-keyword">this</span>.<span class="cm-property">routes</span>) {
<span class="cm-keyword">let</span> <span class="cm-def">match</span> <span class="cm-operator">=</span> <span class="cm-variable-2">url</span>.<span class="cm-property">exec</span>(<span class="cm-variable-2">path</span>);
<span class="cm-keyword">if</span> (<span class="cm-operator">!</span><span class="cm-variable-2">match</span> <span class="cm-operator">|</span><span class="cm-operator">|</span> <span class="cm-variable-2">request</span>.<span class="cm-property">method</span> <span class="cm-operator">!=</span> <span class="cm-variable-2">method</span>) <span class="cm-keyword">continue</span>;
<span class="cm-keyword">let</span> <span class="cm-def">urlParts</span> <span class="cm-operator">=</span> <span class="cm-variable-2">match</span>.<span class="cm-property">slice</span>(<span class="cm-number">1</span>).<span class="cm-property">map</span>(<span class="cm-variable">decodeURIComponent</span>);
<span class="cm-keyword">return</span> <span class="cm-variable-2">handler</span>(<span class="cm-variable-2">context</span>, <span class="cm-meta">...</span><span class="cm-variable-2">urlParts</span>, <span class="cm-variable-2">request</span>);
}
<span class="cm-keyword">return</span> <span class="cm-atom">null</span>;
}
};</pre>
<p><a class="p_ident" id="p_B1LNzaD5gU" href="#p_B1LNzaD5gU" tabindex="-1" role="presentation"></a>ماژول ما کلاس <code>Router</code> را صادر (export) میکند. یک شیء router امکان ثبت گردانندههای جدید را به وسیلهی متد <code>add</code> فراهم میسازد و میتواند درخواستها را به وسیلهی متد <code>resolve</code> نتیجهیابی کند.</p>
<p>متد دوم در صورت پیدا کردن یک گرداننده، یک پاسخ برمیگرداند و درغیر این صورت، مقدار <code>null</code> را تولید میکند. این متد مسیرها را یک به یک (به ترتیبی که تعریف شده اند) امتحان میکند تا زمانی که مسیر منطبق پیدا شود.</p>
<p><a class="p_ident" id="p_vvT6vbNyGE" href="#p_vvT6vbNyGE" tabindex="-1" role="presentation"></a>توابع گرداننده با مقدار <code>context</code> (که در اینجا نمونهی سرویسدهنده است)، رشتههای تطبیقی برای هر گروهی که در عبارت باقاعدهشان تعریف شده، و شیء درخواست(request)، فراخوانی میشوند. رشته باید کدگشایی URL شده باشد زیرا ممکن است URL دارای کدهای شبیه به <code>%20</code> باشد.</p>
<h3><a class="i_ident" id="i_N5JAAJOW7f" href="#i_N5JAAJOW7f" tabindex="-1" role="presentation"></a>سرو کردن فایلها</h3>
<p><a class="p_ident" id="p_FCIt1nZ0q8" href="#p_FCIt1nZ0q8" tabindex="-1" role="presentation"></a>زمانی که درخواستی با هیچیک از انواع درخواست تعریف شده در مسیرگزین ما (router) تطبیق نمییابد، سرویسدهنده باید آن درخواست را درخواستی برای یک فایل در پوشهی <code>public</code> تفسیر کند. میتوان از سرویسدهندهی فایلی که در <a href="20_node.html#file_server">فصل 20</a> ایجاد شد برای سرو اینگونه فایلها استفاده کرد، اما ما نه نیاز به پشتیبانی از <code>PUT</code> و <code>DELETE</code> داریم نه قصد آن را داریم، همچنین دوست داریم ویژگیهای پیشرفتهای مثل پشتیبانی از حافظهی نهان (کش) را داشته باشیم. پس اجازه بدهید از یک بستهی آزمایششده و کارآمد سرویسدهندهی فایل موجود در NPM استفاده کنیم.</p>
<p><a class="p_ident" id="p_fZTrvwf1QX" href="#p_fZTrvwf1QX" tabindex="-1" role="presentation"></a>انتخاب من <code>ecstatic</code> بود. خب این فقط تنها سرویسدهندهی موجود در NPM نبود، اما برای کار ما مناسب است و به خوبی کار میکند. بستهی <code>ecstatic</code> تابعی را فراهم میسازد که میتوان آن را با یک شیء تنظیمات فراخواند تا یک تابع گردانندهی درخواست به وجود آورد. ما از گزینهی <code>root</code> استفاده میکنیم تا به سرویسدهنده اعلام کنیم که کجا باید به دنبال فایلها بگردد. تابع گرداننده پارامترهای <code>response</code> و <code>request</code> را دریافت می کند و میتوان آن را مستقیما به <code>createServer</code> فرستاد تا سرویسدهندهای ایجاد کنیم که فقط فایلها را سرو میکند. با توجه به اینکه قصد داریم ابتدا درخواستهایی را بررسی کنیم که باید به صورت خاص رسیدگی شوند، بنابراین آن را توسط تابع دیگری پوشش میدهیم.</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_A9joeSjy6R" href="#c_A9joeSjy6R" tabindex="-1" role="presentation"></a><span class="cm-keyword">const</span> {<span class="cm-def">createServer</span>} <span class="cm-operator">=</span> <span class="cm-variable">require</span>(<span class="cm-string">"http"</span>);
<span class="cm-keyword">const</span> <span class="cm-def">Router</span> <span class="cm-operator">=</span> <span class="cm-variable">require</span>(<span class="cm-string">"./router"</span>);
<span class="cm-keyword">const</span> <span class="cm-def">ecstatic</span> <span class="cm-operator">=</span> <span class="cm-variable">require</span>(<span class="cm-string">"ecstatic"</span>);
<span class="cm-keyword">const</span> <span class="cm-def">router</span> <span class="cm-operator">=</span> <span class="cm-keyword">new</span> <span class="cm-variable">Router</span>();
<span class="cm-keyword">const</span> <span class="cm-def">defaultHeaders</span> <span class="cm-operator">=</span> {<span class="cm-string cm-property">"Content-Type"</span>: <span class="cm-string">"text/plain"</span>};
<span class="cm-keyword">class</span> <span class="cm-def">SkillShareServer</span> {
<span class="cm-property">constructor</span>(<span class="cm-def">talks</span>) {
<span class="cm-keyword">this</span>.<span class="cm-property">talks</span> <span class="cm-operator">=</span> <span class="cm-variable-2">talks</span>;
<span class="cm-keyword">this</span>.<span class="cm-property">version</span> <span class="cm-operator">=</span> <span class="cm-number">0</span>;
<span class="cm-keyword">this</span>.<span class="cm-property">waiting</span> <span class="cm-operator">=</span> [];
<span class="cm-keyword">let</span> <span class="cm-def">fileServer</span> <span class="cm-operator">=</span> <span class="cm-variable">ecstatic</span>({<span class="cm-property">root</span>: <span class="cm-string">"./public"</span>});
<span class="cm-keyword">this</span>.<span class="cm-property">server</span> <span class="cm-operator">=</span> <span class="cm-variable">createServer</span>((<span class="cm-def">request</span>, <span class="cm-def">response</span>) <span class="cm-operator">=></span> {
<span class="cm-keyword">let</span> <span class="cm-def">resolved</span> <span class="cm-operator">=</span> <span class="cm-variable">router</span>.<span class="cm-property">resolve</span>(<span class="cm-keyword">this</span>, <span class="cm-variable-2">request</span>);
<span class="cm-keyword">if</span> (<span class="cm-variable-2">resolved</span>) {
<span class="cm-variable-2">resolved</span>.<span class="cm-property">catch</span>(<span class="cm-def">error</span> <span class="cm-operator">=></span> {
<span class="cm-keyword">if</span> (<span class="cm-variable-2">error</span>.<span class="cm-property">status</span> <span class="cm-operator">!=</span> <span class="cm-atom">null</span>) <span class="cm-keyword">return</span> <span class="cm-variable-2">error</span>;
<span class="cm-keyword">return</span> {<span class="cm-property">body</span>: <span class="cm-variable">String</span>(<span class="cm-variable-2">error</span>), <span class="cm-property">status</span>: <span class="cm-number">500</span>};
}).<span class="cm-property">then</span>(({<span class="cm-property">body</span>,
<span class="cm-property">status</span> <span class="cm-operator">=</span> <span class="cm-number">200</span>,
<span class="cm-variable">headers</span> <span class="cm-operator">=</span> <span class="cm-variable">defaultHeaders</span>}) <span class="cm-operator">=></span> {
<span class="cm-variable-2">response</span>.<span class="cm-property">writeHead</span>(<span class="cm-variable">status</span>, <span class="cm-variable">headers</span>);
<span class="cm-variable-2">response</span>.<span class="cm-property">end</span>(<span class="cm-variable">body</span>);
});
} <span class="cm-keyword">else</span> {
<span class="cm-variable-2">fileServer</span>(<span class="cm-variable-2">request</span>, <span class="cm-variable-2">response</span>);
}
});
}
<span class="cm-property">start</span>(<span class="cm-def">port</span>) {
<span class="cm-keyword">this</span>.<span class="cm-property">server</span>.<span class="cm-property">listen</span>(<span class="cm-variable-2">port</span>);
}
<span class="cm-property">stop</span>() {
<span class="cm-keyword">this</span>.<span class="cm-property">server</span>.<span class="cm-property">close</span>();
}
}</pre>
<p><a class="p_ident" id="p_jC/YRC/GWd" href="#p_jC/YRC/GWd" tabindex="-1" role="presentation"></a>کد بالا برای پاسخها از سبکی مشابه سرویسدهندهی فایل <a href="20_node.html">فصل پیش</a> استفاده میکند- گردانندهها promise برمیگردانند که به اشیائی منتج میشوند که پاسخ را مشخص میکنند. سرویس دهنده درون یک شیء قرار میگیرد که همچنین وضعیت آن را نیز نگهداری میکند.</p>
<h3><a class="i_ident" id="i_5epvyoayIm" href="#i_5epvyoayIm" tabindex="-1" role="presentation"></a>ارائهها به صورت منابع</h3>
<p>ارائههای پیشنهادی در خاصیت <code>talks</code> سرویسدهنده ذخیره میشوند، شیئی که نام خاصیتهای آن عنوانهای ارائهها میباشد. این ارائهها به عنوان منابع HTTP در آدرس <bdo><code>/talks/[title]</code></bdo> در معرض دسترسی قرار میگیرند، بنابراین ما نیاز به گردانندههایی داریم که به مسیرگزینمان اضافه شوند و متدهای متنوعی که کلاینتها میتوانند برای کار با آنها استفاده کنند را پیاده سازی کنند.</p>
<p><a class="p_ident" id="p_Jg9jMkhozW" href="#p_Jg9jMkhozW" tabindex="-1" role="presentation"></a>گردانندهی درخواستهای <code>GET</code> برای دریافت یک ارائه باید به دنبال ارائه بگردد و پاسخی حاوی اطلاعات ارائه به صورت JSON یا یک خطای 404 را برگرداند.</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_be1zJWaizQ" href="#c_be1zJWaizQ" tabindex="-1" role="presentation"></a><span class="cm-keyword">const</span> <span class="cm-def">talkPath</span> <span class="cm-operator">=</span> <span class="cm-string-2">/^\/talks\/([^\/]+)$/</span>;
<span class="cm-variable">router</span>.<span class="cm-property">add</span>(<span class="cm-string">"GET"</span>, <span class="cm-variable">talkPath</span>, <span class="cm-keyword">async</span> (<span class="cm-def">server</span>, <span class="cm-def">title</span>) <span class="cm-operator">=></span> {
<span class="cm-keyword">if</span> (<span class="cm-variable-2">title</span> <span class="cm-keyword">in</span> <span class="cm-variable-2">server</span>.<span class="cm-property">talks</span>) {
<span class="cm-keyword">return</span> {<span class="cm-property">body</span>: <span class="cm-variable">JSON</span>.<span class="cm-property">stringify</span>(<span class="cm-variable-2">server</span>.<span class="cm-property">talks</span>[<span class="cm-variable-2">title</span>]),
<span class="cm-property">headers</span>: {<span class="cm-string cm-property">"Content-Type"</span>: <span class="cm-string">"application/json"</span>}};
} <span class="cm-keyword">else</span> {
<span class="cm-keyword">return</span> {<span class="cm-property">status</span>: <span class="cm-number">404</span>, <span class="cm-property">body</span>: <span class="cm-string-2">`No talk '${</span><span class="cm-variable-2">title</span><span class="cm-string-2">}</span><span class="cm-string-2">' found`</span>};
}
});</pre>
<p>حذف یک ارائه با پاک کردن آن از شیء <code>talks</code> صورت میگیرد.</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_8nIC54r4QK" href="#c_8nIC54r4QK" tabindex="-1" role="presentation"></a><span class="cm-variable">router</span>.<span class="cm-property">add</span>(<span class="cm-string">"DELETE"</span>, <span class="cm-variable">talkPath</span>, <span class="cm-keyword">async</span> (<span class="cm-def">server</span>, <span class="cm-def">title</span>) <span class="cm-operator">=></span> {
<span class="cm-keyword">if</span> (<span class="cm-variable-2">title</span> <span class="cm-keyword">in</span> <span class="cm-variable-2">server</span>.<span class="cm-property">talks</span>) {
<span class="cm-keyword">delete</span> <span class="cm-variable-2">server</span>.<span class="cm-property">talks</span>[<span class="cm-variable-2">title</span>];
<span class="cm-variable-2">server</span>.<span class="cm-property">updated</span>();
}
<span class="cm-keyword">return</span> {<span class="cm-property">status</span>: <span class="cm-number">204</span>};
});</pre>
<p>متد <code>updated</code>، که در <a href="21_skillsharing.html#updated">ادامه</a> آن را تعریف خواهیم کرد، درخواستهای long polling را از وجود تغییر باخبر میکند.</p>
<p>برای بازیابی محتوای یک بدنهی درخواست، تابعی تعریف میکنیم که <code>readStream</code> نام دارد، که همهی محتوای یک استریم قابل خواندن را میخواند و یک promise برمیگرداند که به یک رشته منتج میشود.</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_ZF0BYHe7yQ" href="#c_ZF0BYHe7yQ" tabindex="-1" role="presentation"></a><span class="cm-keyword">function</span> <span class="cm-def">readStream</span>(<span class="cm-def">stream</span>) {
<span class="cm-keyword">return</span> <span class="cm-keyword">new</span> <span class="cm-variable">Promise</span>((<span class="cm-def">resolve</span>, <span class="cm-def">reject</span>) <span class="cm-operator">=></span> {
<span class="cm-keyword">let</span> <span class="cm-def">data</span> <span class="cm-operator">=</span> <span class="cm-string">""</span>;
<span class="cm-variable-2">stream</span>.<span class="cm-property">on</span>(<span class="cm-string">"error"</span>, <span class="cm-variable-2">reject</span>);
<span class="cm-variable-2">stream</span>.<span class="cm-property">on</span>(<span class="cm-string">"data"</span>, <span class="cm-def">chunk</span> <span class="cm-operator">=></span> <span class="cm-variable-2">data</span> <span class="cm-operator">+=</span> <span class="cm-variable-2">chunk</span>.<span class="cm-property">toString</span>());
<span class="cm-variable-2">stream</span>.<span class="cm-property">on</span>(<span class="cm-string">"end"</span>, () <span class="cm-operator">=></span> <span class="cm-variable-2">resolve</span>(<span class="cm-variable-2">data</span>));
});
}</pre>
<p>گردانندهای که نیاز است بدنههای درخواستها را بخواند گردانندهی <code>PUT</code> میباشد، که این گرداننده برای ایجاد ارائههای جدید استفاده میشود. بررسی اینکه دادههای داده شده دارای خاصیتهای <code>presenter</code> و <code>summary</code> باشد به عهدهای این گرداننده است. دادههایی که از بیرون از سیستم میآیند ممکن است بیمعنا باشد، و ما قصد نداریم مدل داده درونیمان را خراب کنیم یا در صورت دریافت درخواستی بد، سرویسدهنده از کار بیفتد.</p>
<p>اگر دادهها معتبر باشند، گرداننده شیئی که نمایانگر ارائه جدید است را در شیء <code>talks</code> ذخیره میکند، احتمالا ارائه ای با همین نام را بازنویسی میکند و دوباره تابع <code>updated</code> را فراخوانی میکند.</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_C274CtJA7L" href="#c_C274CtJA7L" tabindex="-1" role="presentation"></a><span class="cm-variable">router</span>.<span class="cm-property">add</span>(<span class="cm-string">"PUT"</span>, <span class="cm-variable">talkPath</span>,
<span class="cm-keyword">async</span> (<span class="cm-def">server</span>, <span class="cm-def">title</span>, <span class="cm-def">request</span>) <span class="cm-operator">=></span> {
<span class="cm-keyword">let</span> <span class="cm-def">requestBody</span> <span class="cm-operator">=</span> <span class="cm-keyword">await</span> <span class="cm-variable">readStream</span>(<span class="cm-variable-2">request</span>);
<span class="cm-keyword">let</span> <span class="cm-def">talk</span>;
<span class="cm-keyword">try</span> { <span class="cm-variable-2">talk</span> <span class="cm-operator">=</span> <span class="cm-variable">JSON</span>.<span class="cm-property">parse</span>(<span class="cm-variable-2">requestBody</span>); }
<span class="cm-keyword">catch</span> (<span class="cm-def">_</span>) { <span class="cm-keyword">return</span> {<span class="cm-property">status</span>: <span class="cm-number">400</span>, <span class="cm-property">body</span>: <span class="cm-string">"Invalid JSON"</span>}; }
<span class="cm-keyword">if</span> (<span class="cm-operator">!</span><span class="cm-variable-2">talk</span> <span class="cm-operator">|</span><span class="cm-operator">|</span>
<span class="cm-keyword">typeof</span> <span class="cm-variable-2">talk</span>.<span class="cm-property">presenter</span> <span class="cm-operator">!=</span> <span class="cm-string">"string"</span> <span class="cm-operator">|</span><span class="cm-operator">|</span>
<span class="cm-keyword">typeof</span> <span class="cm-variable-2">talk</span>.<span class="cm-property">summary</span> <span class="cm-operator">!=</span> <span class="cm-string">"string"</span>) {
<span class="cm-keyword">return</span> {<span class="cm-property">status</span>: <span class="cm-number">400</span>, <span class="cm-property">body</span>: <span class="cm-string">"Bad talk data"</span>};
}
<span class="cm-variable-2">server</span>.<span class="cm-property">talks</span>[<span class="cm-variable-2">title</span>] <span class="cm-operator">=</span> {<span class="cm-property">title</span>,
<span class="cm-property">presenter</span>: <span class="cm-variable-2">talk</span>.<span class="cm-property">presenter</span>,
<span class="cm-property">summary</span>: <span class="cm-variable-2">talk</span>.<span class="cm-property">summary</span>,
<span class="cm-property">comments</span>: []};
<span class="cm-variable-2">server</span>.<span class="cm-property">updated</span>();
<span class="cm-keyword">return</span> {<span class="cm-property">status</span>: <span class="cm-number">204</span>};
});</pre>
<p>افزودن یک نظر به یک ارائه به همین صورت است. از <code>readStream</code> برای گرفتن محتوای درخواست استفاده می کنیم، دادهی به دست آمده را اعتبارسنجی میکنیم و در صورت معتبر بودن آن را به صورت یک نظر ذخیره میکنیم.</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_Wnh7sKEOny" href="#c_Wnh7sKEOny" tabindex="-1" role="presentation"></a><span class="cm-variable">router</span>.<span class="cm-property">add</span>(<span class="cm-string">"POST"</span>, <span class="cm-string-2">/^\/talks\/([^\/]+)\/comments$/</span>,
<span class="cm-keyword">async</span> (<span class="cm-def">server</span>, <span class="cm-def">title</span>, <span class="cm-def">request</span>) <span class="cm-operator">=></span> {
<span class="cm-keyword">let</span> <span class="cm-def">requestBody</span> <span class="cm-operator">=</span> <span class="cm-keyword">await</span> <span class="cm-variable">readStream</span>(<span class="cm-variable-2">request</span>);
<span class="cm-keyword">let</span> <span class="cm-def">comment</span>;
<span class="cm-keyword">try</span> { <span class="cm-variable-2">comment</span> <span class="cm-operator">=</span> <span class="cm-variable">JSON</span>.<span class="cm-property">parse</span>(<span class="cm-variable-2">requestBody</span>); }
<span class="cm-keyword">catch</span> (<span class="cm-def">_</span>) { <span class="cm-keyword">return</span> {<span class="cm-property">status</span>: <span class="cm-number">400</span>, <span class="cm-property">body</span>: <span class="cm-string">"Invalid JSON"</span>}; }
<span class="cm-keyword">if</span> (<span class="cm-operator">!</span><span class="cm-variable-2">comment</span> <span class="cm-operator">|</span><span class="cm-operator">|</span>
<span class="cm-keyword">typeof</span> <span class="cm-variable-2">comment</span>.<span class="cm-property">author</span> <span class="cm-operator">!=</span> <span class="cm-string">"string"</span> <span class="cm-operator">|</span><span class="cm-operator">|</span>
<span class="cm-keyword">typeof</span> <span class="cm-variable-2">comment</span>.<span class="cm-property">message</span> <span class="cm-operator">!=</span> <span class="cm-string">"string"</span>) {
<span class="cm-keyword">return</span> {<span class="cm-property">status</span>: <span class="cm-number">400</span>, <span class="cm-property">body</span>: <span class="cm-string">"Bad comment data"</span>};
} <span class="cm-keyword">else</span> <span class="cm-keyword">if</span> (<span class="cm-variable-2">title</span> <span class="cm-keyword">in</span> <span class="cm-variable-2">server</span>.<span class="cm-property">talks</span>) {
<span class="cm-variable-2">server</span>.<span class="cm-property">talks</span>[<span class="cm-variable-2">title</span>].<span class="cm-property">comments</span>.<span class="cm-property">push</span>(<span class="cm-variable-2">comment</span>);
<span class="cm-variable-2">server</span>.<span class="cm-property">updated</span>();
<span class="cm-keyword">return</span> {<span class="cm-property">status</span>: <span class="cm-number">204</span>};
} <span class="cm-keyword">else</span> {
<span class="cm-keyword">return</span> {<span class="cm-property">status</span>: <span class="cm-number">404</span>, <span class="cm-property">body</span>: <span class="cm-string-2">`No talk '${</span><span class="cm-variable-2">title</span><span class="cm-string-2">}</span><span class="cm-string-2">' found`</span>};
}
});</pre>
<p><a class="p_ident" id="p_w1qfxSu1Vs" href="#p_w1qfxSu1Vs" tabindex="-1" role="presentation"></a>تلاش برای اضافه کردن یک نظر به ارائه ای که وجود ندارد منجر به بازگشتن خطای 404 میگردد.</p>
<h3><a class="i_ident" id="i_74Nx/d0epr" href="#i_74Nx/d0epr" tabindex="-1" role="presentation"></a>پشتیبانی از Long Polling</h3>
<p><a class="p_ident" id="p_5HtCGZgsWE" href="#p_5HtCGZgsWE" tabindex="-1" role="presentation"></a>جالب ترین بخش سرویسدهنده، بخشی است که تکنیک long polling را انجام میدهد. زمانی که یک درخواست <code>GET</code> برای <bdo><code>/talks</code></bdo> دریافت میشود، ممکن است یک درخواست معمولی یا یک درخواست به سبک long polling باشد.</p>
<p>با توجه به اینکه در موارد متعددی لازم است که آرایهای از ارائهها را به کلاینت ارسال کنیم، ابتدا یک متد کمکی تعریف می کنیم که این آرایه را برای ما بسازد و سرنام <code>ETag</code> را در پاسخ قرار دهد.</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_AyH6JDZc/B" href="#c_AyH6JDZc/B" tabindex="-1" role="presentation"></a><span class="cm-variable">SkillShareServer</span>.<span class="cm-property">prototype</span>.<span class="cm-property">talkResponse</span> <span class="cm-operator">=</span> <span class="cm-keyword">function</span>() {
<span class="cm-keyword">let</span> <span class="cm-def">talks</span> <span class="cm-operator">=</span> [];
<span class="cm-keyword">for</span> (<span class="cm-keyword">let</span> <span class="cm-def">title</span> <span class="cm-keyword">of</span> <span class="cm-variable">Object</span>.<span class="cm-property">keys</span>(<span class="cm-keyword">this</span>.<span class="cm-property">talks</span>)) {
<span class="cm-variable-2">talks</span>.<span class="cm-property">push</span>(<span class="cm-keyword">this</span>.<span class="cm-property">talks</span>[<span class="cm-variable-2">title</span>]);
}
<span class="cm-keyword">return</span> {
<span class="cm-property">body</span>: <span class="cm-variable">JSON</span>.<span class="cm-property">stringify</span>(<span class="cm-variable-2">talks</span>),
<span class="cm-property">headers</span>: {<span class="cm-string cm-property">"Content-Type"</span>: <span class="cm-string">"application/json"</span>,
<span class="cm-string cm-property">"ETag"</span>: <span class="cm-string-2">`"${</span><span class="cm-keyword">this</span>.<span class="cm-property">version</span><span class="cm-string-2">}</span><span class="cm-string-2">"`</span>}
};
};</pre>
<p>گرداننده خود نیاز دارد تا سرنامهای درخواست را بررسی کند تا مطمئن شود سرنامهای <code>If-None-Match</code> و <code>Prefer</code> موجود باشند. Node سرنامها را که نامشان به صورت غیرحساس به بزرگی/کوچکی حروف مشخص میشود را با حروف کوچک ذخیره میکند.</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_/t9cPK34U3" href="#c_/t9cPK34U3" tabindex="-1" role="presentation"></a><span class="cm-variable">router</span>.<span class="cm-property">add</span>(<span class="cm-string">"GET"</span>, <span class="cm-string-2">/^\/talks$/</span>, <span class="cm-keyword">async</span> (<span class="cm-def">server</span>, <span class="cm-def">request</span>) <span class="cm-operator">=></span> {
<span class="cm-keyword">let</span> <span class="cm-def">tag</span> <span class="cm-operator">=</span> <span class="cm-string-2">/"(.*)"/</span>.<span class="cm-property">exec</span>(<span class="cm-variable-2">request</span>.<span class="cm-property">headers</span>[<span class="cm-string">"if-none-match"</span>]);
<span class="cm-keyword">let</span> <span class="cm-def">wait</span> <span class="cm-operator">=</span> <span class="cm-string-2">/\bwait=(\d+)/</span>.<span class="cm-property">exec</span>(<span class="cm-variable-2">request</span>.<span class="cm-property">headers</span>[<span class="cm-string">"prefer"</span>]);
<span class="cm-keyword">if</span> (<span class="cm-operator">!</span><span class="cm-variable-2">tag</span> <span class="cm-operator">|</span><span class="cm-operator">|</span> <span class="cm-variable-2">tag</span>[<span class="cm-number">1</span>] <span class="cm-operator">!=</span> <span class="cm-variable-2">server</span>.<span class="cm-property">version</span>) {
<span class="cm-keyword">return</span> <span class="cm-variable-2">server</span>.<span class="cm-property">talkResponse</span>();
} <span class="cm-keyword">else</span> <span class="cm-keyword">if</span> (<span class="cm-operator">!</span><span class="cm-variable-2">wait</span>) {
<span class="cm-keyword">return</span> {<span class="cm-property">status</span>: <span class="cm-number">304</span>};
} <span class="cm-keyword">else</span> {
<span class="cm-keyword">return</span> <span class="cm-variable-2">server</span>.<span class="cm-property">waitForChanges</span>(<span class="cm-variable">Number</span>(<span class="cm-variable-2">wait</span>[<span class="cm-number">1</span>]));
}
});</pre>
<p>اگر برچسبی داده نشده بود یا برچسب داده شده با نسخهی کنونی سرویسدهنده منطبق نبود، گرداننده لیست ارائه ها را برمیگرداند. اگر درخواست شرطی باشد و ارائه ها تغییری نکرده باشند، ما سرنام <code>Prefer</code> را بررسی میکنیم تا ببینیم که آیا لازم است پاسخدادن را به تاخییر بیاندازیم یا باید سریع پاسخ دهیم.</p>
<p><a class="p_ident" id="p_HZz/rPYIcP" href="#p_HZz/rPYIcP" tabindex="-1" role="presentation"></a>توابع callback برای درخواستهایی که به تاخیر انداخته شده اند در آرایهی <code>waiting</code> سرویسدهنده ذخیره میشوند در نتیجه در هنگام تغییر چیزی میتوان آنها را باخبر کرد. متد <code>waitForChanges</code> همچنین بلافاصله یک زمانسنج برای پاسخ با یک کد وضعیت 304 تنظیم میکند که در صورتیکه درخواست به مدت طولانی منتظر بماند عمل خواهد کرد.</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_4a/A2U+gT3" href="#c_4a/A2U+gT3" tabindex="-1" role="presentation"></a><span class="cm-variable">SkillShareServer</span>.<span class="cm-property">prototype</span>.<span class="cm-property">waitForChanges</span> <span class="cm-operator">=</span> <span class="cm-keyword">function</span>(<span class="cm-def">time</span>) {
<span class="cm-keyword">return</span> <span class="cm-keyword">new</span> <span class="cm-variable">Promise</span>(<span class="cm-def">resolve</span> <span class="cm-operator">=></span> {
<span class="cm-keyword">this</span>.<span class="cm-property">waiting</span>.<span class="cm-property">push</span>(<span class="cm-variable-2">resolve</span>);
<span class="cm-variable">setTimeout</span>(() <span class="cm-operator">=></span> {
<span class="cm-keyword">if</span> (<span class="cm-operator">!</span><span class="cm-keyword">this</span>.<span class="cm-property">waiting</span>.<span class="cm-property">includes</span>(<span class="cm-variable-2">resolve</span>)) <span class="cm-keyword">return</span>;
<span class="cm-keyword">this</span>.<span class="cm-property">waiting</span> <span class="cm-operator">=</span> <span class="cm-keyword">this</span>.<span class="cm-property">waiting</span>.<span class="cm-property">filter</span>(<span class="cm-def">r</span> <span class="cm-operator">=></span> <span class="cm-variable-2">r</span> <span class="cm-operator">!=</span> <span class="cm-variable-2">resolve</span>);
<span class="cm-variable-2">resolve</span>({<span class="cm-property">status</span>: <span class="cm-number">304</span>});
}, <span class="cm-variable-2">time</span> <span class="cm-operator">*</span> <span class="cm-number">1000</span>);
});
};</pre>
<p id="updated">ثبت یک تغییر به وسیلهی <code>updated</code> باعث افزایش شماره نسخه، خاصیت <code>version</code>، میشود و همهی درخواستهای در انتظار را بیدار میکند.</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_mZ7p2D8VNW" href="#c_mZ7p2D8VNW" tabindex="-1" role="presentation"></a><span class="cm-variable">SkillShareServer</span>.<span class="cm-property">prototype</span>.<span class="cm-property">updated</span> <span class="cm-operator">=</span> <span class="cm-keyword">function</span>() {
<span class="cm-keyword">this</span>.<span class="cm-property">version</span><span class="cm-operator">++</span>;
<span class="cm-keyword">let</span> <span class="cm-def">response</span> <span class="cm-operator">=</span> <span class="cm-keyword">this</span>.<span class="cm-property">talkResponse</span>();
<span class="cm-keyword">this</span>.<span class="cm-property">waiting</span>.<span class="cm-property">forEach</span>(<span class="cm-def">resolve</span> <span class="cm-operator">=></span> <span class="cm-variable-2">resolve</span>(<span class="cm-variable-2">response</span>));
<span class="cm-keyword">this</span>.<span class="cm-property">waiting</span> <span class="cm-operator">=</span> [];
};</pre>
<p><a class="p_ident" id="p_LeSawPziI9" href="#p_LeSawPziI9" tabindex="-1" role="presentation"></a>کد سرویسدهنده اینجا به پایان میرسد. اگر ما یک نمونه از <code>SkillShareServer</code> ایجاد کرده و روی درگاه 8000 اجرا کنیم، سرویسدهندهی ایجاد شده، فایل ها را از زیرپوشهی <code>public</code> به همراه یک رابط مدیریت ارائه تحت مسیر <bdo><code>/talks</code></bdo> سرو میکند.</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_7jK2W+D0gW" href="#c_7jK2W+D0gW" tabindex="-1" role="presentation"></a><span class="cm-keyword">new</span> <span class="cm-variable">SkillShareServer</span>(<span class="cm-variable">Object</span>.<span class="cm-property">create</span>(<span class="cm-atom">null</span>)).<span class="cm-property">start</span>(<span class="cm-number">8000</span>);</pre>
<h2><a class="h_ident" id="h_V10Z2s/Z1m" href="#h_V10Z2s/Z1m" tabindex="-1" role="presentation"></a>کلاینت</h2>
<p><a class="p_ident" id="p_hrwPpXZ3Tx" href="#p_hrwPpXZ3Tx" tabindex="-1" role="presentation"></a>بخش مربوط به کلاینت وبسایت اشتراک مهارت از سه فایل تشکیل میشود: یک صفحهی HTML ساده، یک برگهی سبک CSS، و یک فایل جاوااسکریپت.</p>
<h3><a class="i_ident" id="i_n3OM6EV/KR" href="#i_n3OM6EV/KR" tabindex="-1" role="presentation"></a>HTML</h3>
<p>یکی از قراردادهای بسیار پراستفاده در سرویسدهندههای وب این است که در صورت دریافت درخواستی مستقیم به مسیری که به یک پوشه ختم میشود، سرویسدهنده تلاش میکند تا فایلی به نام <code>index.html</code> را سرو کند. ماژول سرویسدهندهی فایلی که ما استفاده میکنیم، <code>ecstatic</code>، از این قرارداد پشتیبانی میکند. زمانی که یک درخواست به مسیر <code>/</code> ارسال میشود، سرویسدهنده به دنبال فایل <bdo><code>./<wbr>public/<wbr>index.<wbr>html</code></bdo> میگردد (<bdo><code>./public</code></bdo> ریشهای است که ما تعیین کرده ایم) و آن فایل را در صورت وجود بازمیگرداند.</p>
<p><a class="p_ident" id="p_5UDN0TKLKy" href="#p_5UDN0TKLKy" tabindex="-1" role="presentation"></a>بنابراین، اگر قصد داریم صفحهای را در هنگام باز شدن سرویسدهندهمان نمایش دهیم، باید آن صفحه را در <bdo><code>public/<wbr>index.<wbr>html</code></bdo> قرار دهیم. فایل index ما به شکل زیر میباشد:</p>
<pre class="snippet cm-s-default" data-language="text/html" ><a class="c_ident" id="c_ysNDoq7Ue1" href="#c_ysNDoq7Ue1" tabindex="-1" role="presentation"></a><span class="cm-meta"><!doctype html></span>
<span class="cm-tag cm-bracket"><</span><span class="cm-tag">meta</span> <span class="cm-attribute">charset</span>=<span class="cm-string">"utf-8"</span><span class="cm-tag cm-bracket">></span>
<span class="cm-tag cm-bracket"><</span><span class="cm-tag">title</span><span class="cm-tag cm-bracket">></span>Skill Sharing<span class="cm-tag cm-bracket"></</span><span class="cm-tag">title</span><span class="cm-tag cm-bracket">></span>
<span class="cm-tag cm-bracket"><</span><span class="cm-tag">link</span> <span class="cm-attribute">rel</span>=<span class="cm-string">"stylesheet"</span> <span class="cm-attribute">href</span>=<span class="cm-string">"skillsharing.css"</span><span class="cm-tag cm-bracket">></span>
<span class="cm-tag cm-bracket"><</span><span class="cm-tag">h1</span><span class="cm-tag cm-bracket">></span>Skill Sharing<span class="cm-tag cm-bracket"></</span><span class="cm-tag">h1</span><span class="cm-tag cm-bracket">></span>
<span class="cm-tag cm-bracket"><</span><span class="cm-tag">script</span> <span class="cm-attribute">src</span>=<span class="cm-string">"skillsharing_client.js"</span><span class="cm-tag cm-bracket">></span><span class="cm-tag cm-bracket"></</span><span class="cm-tag">script</span><span class="cm-tag cm-bracket">></span></pre>
<p><a class="p_ident" id="p_svMoYyDtQo" href="#p_svMoYyDtQo" tabindex="-1" role="presentation"></a>این فایل عنوان صفحه را تعریف کرده و یک فایل CSS اضافه میکند. فایل CSS تعدادی سبک تعریف می کند و علاوه بر چند کار دیگر، فاصلهی بین ارائهها را تنظیم میکند.</p>
<p>در ادامه، یک سرعنوان به بالای صفحه اضافه میکند و اسکریپتی که کد کلاینت اپلیکیشن را دارد را نیز بارگیری میکند.</p>
<h3><a class="i_ident" id="i_gul6HAlA9g" href="#i_gul6HAlA9g" tabindex="-1" role="presentation"></a>کنشها(actions)</h3>
<p><a class="p_ident" id="p_TojnILGjfB" href="#p_TojnILGjfB" tabindex="-1" role="presentation"></a>وضعیت اپلیکیشن حاوی لیست ارائهها و نام کاربر میباشد، و ما آن را در یک شیء <code>{talks, user}</code> ذخیره میکنیم. رابط کاربری مستقیما اجازهی دستکاری وضعیت و ارسال درخواست HTTP را نخواهد داشت. در عوض، رابط کنشها (actions) را گسیل میدهد که عمل مورد نظر کاربر را توضیح میدهند.</p>
<p><a class="p_ident" id="p_NOtMTvAFIH" href="#p_NOtMTvAFIH" tabindex="-1" role="presentation"></a>تابع <code>handleAction</code> یک action گرفته و به آن عمل میکند. با توجه به اینکه بهروزرسانیهای وضعیت خیلی ساده میباشند، تغییر وضعیت در همان تابع صورت میگیرد.</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_JJtzZxGbIS" href="#c_JJtzZxGbIS" tabindex="-1" role="presentation"></a><span class="cm-keyword">function</span> <span class="cm-def">handleAction</span>(<span class="cm-def">state</span>, <span class="cm-def">action</span>) {
<span class="cm-keyword">if</span> (<span class="cm-variable-2">action</span>.<span class="cm-property">type</span> <span class="cm-operator">==</span> <span class="cm-string">"setUser"</span>) {
<span class="cm-variable">localStorage</span>.<span class="cm-property">setItem</span>(<span class="cm-string">"userName"</span>, <span class="cm-variable-2">action</span>.<span class="cm-property">user</span>);
<span class="cm-keyword">return</span> <span class="cm-variable">Object</span>.<span class="cm-property">assign</span>({}, <span class="cm-variable-2">state</span>, {<span class="cm-property">user</span>: <span class="cm-variable-2">action</span>.<span class="cm-property">user</span>});
} <span class="cm-keyword">else</span> <span class="cm-keyword">if</span> (<span class="cm-variable-2">action</span>.<span class="cm-property">type</span> <span class="cm-operator">==</span> <span class="cm-string">"setTalks"</span>) {
<span class="cm-keyword">return</span> <span class="cm-variable">Object</span>.<span class="cm-property">assign</span>({}, <span class="cm-variable-2">state</span>, {<span class="cm-property">talks</span>: <span class="cm-variable-2">action</span>.<span class="cm-property">talks</span>});
} <span class="cm-keyword">else</span> <span class="cm-keyword">if</span> (<span class="cm-variable-2">action</span>.<span class="cm-property">type</span> <span class="cm-operator">==</span> <span class="cm-string">"newTalk"</span>) {
<span class="cm-variable">fetchOK</span>(<span class="cm-variable">talkURL</span>(<span class="cm-variable-2">action</span>.<span class="cm-property">title</span>), {
<span class="cm-property">method</span>: <span class="cm-string">"PUT"</span>,
<span class="cm-property">headers</span>: {<span class="cm-string cm-property">"Content-Type"</span>: <span class="cm-string">"application/json"</span>},
<span class="cm-property">body</span>: <span class="cm-variable">JSON</span>.<span class="cm-property">stringify</span>({
<span class="cm-property">presenter</span>: <span class="cm-variable-2">state</span>.<span class="cm-property">user</span>,
<span class="cm-property">summary</span>: <span class="cm-variable-2">action</span>.<span class="cm-property">summary</span>
})
}).<span class="cm-property">catch</span>(<span class="cm-variable">reportError</span>);
} <span class="cm-keyword">else</span> <span class="cm-keyword">if</span> (<span class="cm-variable-2">action</span>.<span class="cm-property">type</span> <span class="cm-operator">==</span> <span class="cm-string">"deleteTalk"</span>) {
<span class="cm-variable">fetchOK</span>(<span class="cm-variable">talkURL</span>(<span class="cm-variable-2">action</span>.<span class="cm-property">talk</span>), {<span class="cm-property">method</span>: <span class="cm-string">"DELETE"</span>})
.<span class="cm-property">catch</span>(<span class="cm-variable">reportError</span>);
} <span class="cm-keyword">else</span> <span class="cm-keyword">if</span> (<span class="cm-variable-2">action</span>.<span class="cm-property">type</span> <span class="cm-operator">==</span> <span class="cm-string">"newComment"</span>) {
<span class="cm-variable">fetchOK</span>(<span class="cm-variable">talkURL</span>(<span class="cm-variable-2">action</span>.<span class="cm-property">talk</span>) <span class="cm-operator">+</span> <span class="cm-string">"/comments"</span>, {
<span class="cm-property">method</span>: <span class="cm-string">"POST"</span>,
<span class="cm-property">headers</span>: {<span class="cm-string cm-property">"Content-Type"</span>: <span class="cm-string">"application/json"</span>},
<span class="cm-property">body</span>: <span class="cm-variable">JSON</span>.<span class="cm-property">stringify</span>({
<span class="cm-property">author</span>: <span class="cm-variable-2">state</span>.<span class="cm-property">user</span>,
<span class="cm-property">message</span>: <span class="cm-variable-2">action</span>.<span class="cm-property">message</span>
})
}).<span class="cm-property">catch</span>(<span class="cm-variable">reportError</span>);
}
<span class="cm-keyword">return</span> <span class="cm-variable-2">state</span>;
}</pre>
<p>نام کاربر را در <code>localeStorage</code> ذخیره میکنیم در نتیجه میتوان آن را با بارگیری صفحه بازیابی کرد.</p>
<p><a class="p_ident" id="p_SoLS1byDyt" href="#p_SoLS1byDyt" tabindex="-1" role="presentation"></a>این کنشها نیاز دارند تا با سرویسدهنده برای ساخت درخواستهای شبکه با استفاده از <code>fetch</code> و به رابط HTTPای که پیشتر توصیف شد تعامل کنند. ما از یک تابع پوششدهنده به نام <code>fetchOK</code> استفاده میکنیم، که باعث میشود اطمینان حاصل شود که promise برگشتی در صورت تولید خطا توسط سرویسدهنده لغو شود.</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_IM58YE2b7h" href="#c_IM58YE2b7h" tabindex="-1" role="presentation"></a><span class="cm-keyword">function</span> <span class="cm-def">fetchOK</span>(<span class="cm-def">url</span>, <span class="cm-def">options</span>) {
<span class="cm-keyword">return</span> <span class="cm-variable">fetch</span>(<span class="cm-variable-2">url</span>, <span class="cm-variable-2">options</span>).<span class="cm-property">then</span>(<span class="cm-def">response</span> <span class="cm-operator">=></span> {
<span class="cm-keyword">if</span> (<span class="cm-variable-2">response</span>.<span class="cm-property">status</span> <span class="cm-operator"><</span> <span class="cm-number">400</span>) <span class="cm-keyword">return</span> <span class="cm-variable-2">response</span>;
<span class="cm-keyword">else</span> <span class="cm-keyword">throw</span> <span class="cm-keyword">new</span> <span class="cm-variable">Error</span>(<span class="cm-variable-2">response</span>.<span class="cm-property">statusText</span>);
});
}</pre>
<p><a class="p_ident" id="p_Di2bB3ekhc" href="#p_Di2bB3ekhc" tabindex="-1" role="presentation"></a>تابع کمکی زیر برای ساخت یک URL برای یک ارائه با یک عنوان دادهشده استفاده میشود.</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_KDfsRI9rOO" href="#c_KDfsRI9rOO" tabindex="-1" role="presentation"></a><span class="cm-keyword">function</span> <span class="cm-def">talkURL</span>(<span class="cm-def">title</span>) {
<span class="cm-keyword">return</span> <span class="cm-string">"talks/"</span> <span class="cm-operator">+</span> <span class="cm-variable">encodeURIComponent</span>(<span class="cm-variable-2">title</span>);
}</pre>
<p>زمانی که یک درخواست با مشکل روبرو میشود، دوست نداریم صفحهی کاربر بدون هیچ توضیحی ثابت بماند. پس تابعی تعریف میکنیم به نام <code>reportError</code> که حداقل به کاربر متنی را نشان میدهد که چیزی با مشکل روبرو شده است.</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_upWq63EA/j" href="#c_upWq63EA/j" tabindex="-1" role="presentation"></a><span class="cm-keyword">function</span> <span class="cm-def">reportError</span>(<span class="cm-def">error</span>) {
<span class="cm-variable">alert</span>(<span class="cm-variable">String</span>(<span class="cm-variable-2">error</span>));
}</pre>
<h3><a class="i_ident" id="i_qPjf2UZ9Yf" href="#i_qPjf2UZ9Yf" tabindex="-1" role="presentation"></a>ساخت و نمایش مؤلفهها</h3>
<p><a class="p_ident" id="p_Yfb0Ie8yZw" href="#p_Yfb0Ie8yZw" tabindex="-1" role="presentation"></a>از روشی مشابه آنچه در <a href="19_paint.html">فصل 19</a> دیدیم، که تقسیم اپلیکیشن به مؤلفهها بود استفاده میکنیم. اما با توجه به اینکه بعضی از مؤلفهها هرگز نیاز به بهروزرسانی ندارند یا در صورت بهروز شدن، از نو به صورت کامل بازایجاد میشوند، ما آنها را نه بصورت کلاس بلکه به شکل توابعی تعریف میکنیم که مستقیما یک گرهی DOM برمیگردانند. به عنوان مثال، در اینجا مؤلفهای داریم که فیلدی را نشان می دهد که کاربر میتواند نامش را در آن وارد کند:</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_XPxLoKIFvt" href="#c_XPxLoKIFvt" tabindex="-1" role="presentation"></a><span class="cm-keyword">function</span> <span class="cm-def">renderUserField</span>(<span class="cm-def">name</span>, <span class="cm-def">dispatch</span>) {
<span class="cm-keyword">return</span> <span class="cm-variable">elt</span>(<span class="cm-string">"label"</span>, {}, <span class="cm-string">"Your name: "</span>, <span class="cm-variable">elt</span>(<span class="cm-string">"input"</span>, {
<span class="cm-property">type</span>: <span class="cm-string">"text"</span>,
<span class="cm-property">value</span>: <span class="cm-variable-2">name</span>,
<span class="cm-property">onchange</span>(<span class="cm-def">event</span>) {
<span class="cm-variable-2">dispatch</span>({<span class="cm-property">type</span>: <span class="cm-string">"setUser"</span>, <span class="cm-property">user</span>: <span class="cm-variable-2">event</span>.<span class="cm-property">target</span>.<span class="cm-property">value</span>});
}
}));
}</pre>
<p>تابع <code>elt</code> برای ساخت عناصر DOM استفاده میشود، همانی است که در <a href="19_paint.html">فصل 19</a> استفاده میکردیم.</p>
<p>تابع مشابهی برای ساخت و نمایش ارائهها در صفحه استفاده می شود، که لیستی از نظرها و فرمی برای افزودن یک نظر جدید را نیز شامل میشود.</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_VdTnp45oFj" href="#c_VdTnp45oFj" tabindex="-1" role="presentation"></a><span class="cm-keyword">function</span> <span class="cm-def">renderTalk</span>(<span class="cm-def">talk</span>, <span class="cm-def">dispatch</span>) {
<span class="cm-keyword">return</span> <span class="cm-variable">elt</span>(
<span class="cm-string">"section"</span>, {<span class="cm-property">className</span>: <span class="cm-string">"talk"</span>},
<span class="cm-variable">elt</span>(<span class="cm-string">"h2"</span>, <span class="cm-atom">null</span>, <span class="cm-variable-2">talk</span>.<span class="cm-property">title</span>, <span class="cm-string">" "</span>, <span class="cm-variable">elt</span>(<span class="cm-string">"button"</span>, {
<span class="cm-property">type</span>: <span class="cm-string">"button"</span>,
<span class="cm-property">onclick</span>() {
<span class="cm-variable-2">dispatch</span>({<span class="cm-property">type</span>: <span class="cm-string">"deleteTalk"</span>, <span class="cm-property">talk</span>: <span class="cm-variable-2">talk</span>.<span class="cm-property">title</span>});
}
}, <span class="cm-string">"Delete"</span>)),
<span class="cm-variable">elt</span>(<span class="cm-string">"div"</span>, <span class="cm-atom">null</span>, <span class="cm-string">"by "</span>,
<span class="cm-variable">elt</span>(<span class="cm-string">"strong"</span>, <span class="cm-atom">null</span>, <span class="cm-variable-2">talk</span>.<span class="cm-property">presenter</span>)),
<span class="cm-variable">elt</span>(<span class="cm-string">"p"</span>, <span class="cm-atom">null</span>, <span class="cm-variable-2">talk</span>.<span class="cm-property">summary</span>),
<span class="cm-meta">...</span><span class="cm-variable-2">talk</span>.<span class="cm-property">comments</span>.<span class="cm-property">map</span>(<span class="cm-variable">renderComment</span>),
<span class="cm-variable">elt</span>(<span class="cm-string">"form"</span>, {
<span class="cm-property">onsubmit</span>(<span class="cm-def">event</span>) {
<span class="cm-variable-2">event</span>.<span class="cm-property">preventDefault</span>();
<span class="cm-keyword">let</span> <span class="cm-def">form</span> <span class="cm-operator">=</span> <span class="cm-variable-2">event</span>.<span class="cm-property">target</span>;
<span class="cm-variable-2">dispatch</span>({<span class="cm-property">type</span>: <span class="cm-string">"newComment"</span>,
<span class="cm-property">talk</span>: <span class="cm-variable-2">talk</span>.<span class="cm-property">title</span>,
<span class="cm-property">message</span>: <span class="cm-variable-2">form</span>.<span class="cm-property">elements</span>.<span class="cm-property">comment</span>.<span class="cm-property">value</span>});
<span class="cm-variable-2">form</span>.<span class="cm-property">reset</span>();
}
}, <span class="cm-variable">elt</span>(<span class="cm-string">"input"</span>, {<span class="cm-property">type</span>: <span class="cm-string">"text"</span>, <span class="cm-property">name</span>: <span class="cm-string">"comment"</span>}), <span class="cm-string">" "</span>,
<span class="cm-variable">elt</span>(<span class="cm-string">"button"</span>, {<span class="cm-property">type</span>: <span class="cm-string">"submit"</span>}, <span class="cm-string">"Add comment"</span>)));
}</pre>
<p>گرداننده رخداد <code>"submit"</code> متد <code>form.reset</code> را فراخوانی میکند تا محتوای فرم را پس از ایجاد یک کنش <code>"newComment"</code> پاک کند.</p>
<p><a class="p_ident" id="p_W/mFkV7xx6" href="#p_W/mFkV7xx6" tabindex="-1" role="presentation"></a>در صورت ایجاد بخش نسبتا پیچیدهای از DOM، این سبک از برنامهنویسی در ابتدا کمی شلوغ به نظر میرسد. افزونهی پراستفادهای برای جاوااسکریپت (غیراستاندارد) وجود دارد که JSX نام دارد و به شما امکان نوشتن مستقیم HTML درون اسکریپتهای جاوااسکریپت را میدهد که میتواند اینگونه کدها را زیبا تر کند (البته بسته به اینکه شما کد زیبا را چگونه ارزیابی کنید). پیش از اینکه بتوانید این کد را اجرا کنید، باید برنامهای روی اسکریپتتان اجرا کنید تا کدهای شبهHTML را به فراخوانیهای تابع جاوااسکریپت تبدیل کند بسیار شبیه به آن چه اینجا استفاده کردیم.</p>
<p>ساخت و نمایش نظرها ساده تر است.</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_N9+wrVgBuY" href="#c_N9+wrVgBuY" tabindex="-1" role="presentation"></a><span class="cm-keyword">function</span> <span class="cm-def">renderComment</span>(<span class="cm-def">comment</span>) {
<span class="cm-keyword">return</span> <span class="cm-variable">elt</span>(<span class="cm-string">"p"</span>, {<span class="cm-property">className</span>: <span class="cm-string">"comment"</span>},
<span class="cm-variable">elt</span>(<span class="cm-string">"strong"</span>, <span class="cm-atom">null</span>, <span class="cm-variable-2">comment</span>.<span class="cm-property">author</span>),
<span class="cm-string">": "</span>, <span class="cm-variable-2">comment</span>.<span class="cm-property">message</span>);
}</pre>
<p>سرانجام، فرمی که کاربر برای ایجاد یک ارائهی جدید استفاده میکند به صورت زیر ایجاد میشود:</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_CrmrTqBxyk" href="#c_CrmrTqBxyk" tabindex="-1" role="presentation"></a><span class="cm-keyword">function</span> <span class="cm-def">renderTalkForm</span>(<span class="cm-def">dispatch</span>) {
<span class="cm-keyword">let</span> <span class="cm-def">title</span> <span class="cm-operator">=</span> <span class="cm-variable">elt</span>(<span class="cm-string">"input"</span>, {<span class="cm-property">type</span>: <span class="cm-string">"text"</span>});
<span class="cm-keyword">let</span> <span class="cm-def">summary</span> <span class="cm-operator">=</span> <span class="cm-variable">elt</span>(<span class="cm-string">"input"</span>, {<span class="cm-property">type</span>: <span class="cm-string">"text"</span>});
<span class="cm-keyword">return</span> <span class="cm-variable">elt</span>(<span class="cm-string">"form"</span>, {
<span class="cm-property">onsubmit</span>(<span class="cm-def">event</span>) {
<span class="cm-variable-2">event</span>.<span class="cm-property">preventDefault</span>();
<span class="cm-variable-2">dispatch</span>({<span class="cm-property">type</span>: <span class="cm-string">"newTalk"</span>,
<span class="cm-property">title</span>: <span class="cm-variable-2">title</span>.<span class="cm-property">value</span>,
<span class="cm-property">summary</span>: <span class="cm-variable-2">summary</span>.<span class="cm-property">value</span>});
<span class="cm-variable-2">event</span>.<span class="cm-property">target</span>.<span class="cm-property">reset</span>();
}
}, <span class="cm-variable">elt</span>(<span class="cm-string">"h3"</span>, <span class="cm-atom">null</span>, <span class="cm-string">"Submit a Talk"</span>),
<span class="cm-variable">elt</span>(<span class="cm-string">"label"</span>, <span class="cm-atom">null</span>, <span class="cm-string">"Title: "</span>, <span class="cm-variable-2">title</span>),
<span class="cm-variable">elt</span>(<span class="cm-string">"label"</span>, <span class="cm-atom">null</span>, <span class="cm-string">"Summary: "</span>, <span class="cm-variable-2">summary</span>),
<span class="cm-variable">elt</span>(<span class="cm-string">"button"</span>, {<span class="cm-property">type</span>: <span class="cm-string">"submit"</span>}, <span class="cm-string">"Submit"</span>));
}</pre>
<h3><a class="i_ident" id="i_QESJMin8/J" href="#i_QESJMin8/J" tabindex="-1" role="presentation"></a>Polling</h3>
<p><a class="p_ident" id="p_+DwtNCTm0q" href="#p_+DwtNCTm0q" tabindex="-1" role="presentation"></a>برای راهاندازی اپلیکیشن به لیست ارائههای موجود نیاز داریم. به دلیل اینکه بارگیری ابتدایی بسیار به فرایند long polling مرتبط است - <code>ETag</code> به دست آمده از بارگیری باید در هنگام درخواست polling استفاده شود - تابعی خواهیم نوشت که به ارسال درخواست polling به سرویسدهنده برای <bdo><code>/talks</code></bdo> ادامه خواهد داد و هنگامی که مجموعهی جدیدی از ارائهها در دسترس باشد، یک تابع callback فراخوانی میکند .</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_y9uqyFVtpt" href="#c_y9uqyFVtpt" tabindex="-1" role="presentation"></a><span class="cm-keyword">async</span> <span class="cm-keyword">function</span> <span class="cm-def">pollTalks</span>(<span class="cm-def">update</span>) {
<span class="cm-keyword">let</span> <span class="cm-def">tag</span> <span class="cm-operator">=</span> <span class="cm-atom">undefined</span>;
<span class="cm-keyword">for</span> (;;) {
<span class="cm-keyword">let</span> <span class="cm-def">response</span>;
<span class="cm-keyword">try</span> {
<span class="cm-variable-2">response</span> <span class="cm-operator">=</span> <span class="cm-keyword">await</span> <span class="cm-variable">fetchOK</span>(<span class="cm-string">"/talks"</span>, {
<span class="cm-property">headers</span>: <span class="cm-variable-2">tag</span> <span class="cm-operator">&</span><span class="cm-operator">&</span> {<span class="cm-string cm-property">"If-None-Match"</span>: <span class="cm-variable-2">tag</span>,
<span class="cm-string cm-property">"Prefer"</span>: <span class="cm-string">"wait=90"</span>}
});
} <span class="cm-keyword">catch</span> (<span class="cm-def">e</span>) {
<span class="cm-variable">console</span>.<span class="cm-property">log</span>(<span class="cm-string">"Request failed: "</span> <span class="cm-operator">+</span> <span class="cm-variable-2">e</span>);
<span class="cm-keyword">await</span> <span class="cm-keyword">new</span> <span class="cm-variable">Promise</span>(<span class="cm-def">resolve</span> <span class="cm-operator">=></span> <span class="cm-variable">setTimeout</span>(<span class="cm-variable-2">resolve</span>, <span class="cm-number">500</span>));
<span class="cm-keyword">continue</span>;
}
<span class="cm-keyword">if</span> (<span class="cm-variable-2">response</span>.<span class="cm-property">status</span> <span class="cm-operator">==</span> <span class="cm-number">304</span>) <span class="cm-keyword">continue</span>;
<span class="cm-variable-2">tag</span> <span class="cm-operator">=</span> <span class="cm-variable-2">response</span>.<span class="cm-property">headers</span>.<span class="cm-property">get</span>(<span class="cm-string">"ETag"</span>);
<span class="cm-variable-2">update</span>(<span class="cm-keyword">await</span> <span class="cm-variable-2">response</span>.<span class="cm-property">json</span>());
}
}</pre>
<p>این تابع از نوع <code>async</code> میباشد در نتیجه استفاده از حلقه و انتظار برای این درخواست در آن ساده تر خواهد بود. این تابع حلقهای بینهایت را اجرا میکند که در هر تکرار، لیستی از ارائهها را بازیابی میکند، یا به صورت عادی، یا اگر این اولین درخواست نباشد، با سرنامهای اضافه شده که باعث شده این درخواست long polling در نظر گرفته شود.</p>
<p>زمانی که یک درخواست با مشکل روبرو میشود، تابع اندکی صبر میکند و سپس دوباره تلاش میکند. با این کار، اگر اتصال شبکه برای لحظهای قطع شود و دوباره برگردد، نرمافزار میتواند خودش را بازیابی کند و به بهروزرسانی ادامه دهد. promise منتج شده به وسیلهی <code>setTimeout</code> روشی است که تابع <code>async</code> را وادار میسازد اندکی منتظر بماند.</p>
<p><a class="p_ident" id="p_yEIhWQ8IbP" href="#p_yEIhWQ8IbP" tabindex="-1" role="presentation"></a>هنگامیکه سرویسدهنده پاسخی با کد 304 برمیگرداند، معنای آن این است که یک درخواست long polling منقضی شده است، بنابراین تابع باید بدون درنگ به سراغ راهاندازی درخواست بعدی برود. اگر پاسخ یک پاسخ عادی 200 باشد، بدنهی آن به عنوان JSON خوانده شده و به callback ارسال میشود، و مقدار سرنام <code>ETag</code> آن برای تکرار بعدی ذخیره میشود.</p>
<h3><a class="i_ident" id="i_FN+YWo/5QG" href="#i_FN+YWo/5QG" tabindex="-1" role="presentation"></a>اپلیکیشن</h3>
<p>مؤلفهی پیشرو، همهی اجزاء رابط کاربری را گردآوری میکند:</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_PzYGJXojtl" href="#c_PzYGJXojtl" tabindex="-1" role="presentation"></a><span class="cm-keyword">class</span> <span class="cm-def">SkillShareApp</span> {
<span class="cm-property">constructor</span>(<span class="cm-def">state</span>, <span class="cm-def">dispatch</span>) {
<span class="cm-keyword">this</span>.<span class="cm-property">dispatch</span> <span class="cm-operator">=</span> <span class="cm-variable-2">dispatch</span>;
<span class="cm-keyword">this</span>.<span class="cm-property">talkDOM</span> <span class="cm-operator">=</span> <span class="cm-variable">elt</span>(<span class="cm-string">"div"</span>, {<span class="cm-property">className</span>: <span class="cm-string">"talks"</span>});
<span class="cm-keyword">this</span>.<span class="cm-property">dom</span> <span class="cm-operator">=</span> <span class="cm-variable">elt</span>(<span class="cm-string">"div"</span>, <span class="cm-atom">null</span>,
<span class="cm-variable">renderUserField</span>(<span class="cm-variable-2">state</span>.<span class="cm-property">user</span>, <span class="cm-variable-2">dispatch</span>),
<span class="cm-keyword">this</span>.<span class="cm-property">talkDOM</span>,
<span class="cm-variable">renderTalkForm</span>(<span class="cm-variable-2">dispatch</span>));
<span class="cm-keyword">this</span>.<span class="cm-property">syncState</span>(<span class="cm-variable-2">state</span>);
}
<span class="cm-property">syncState</span>(<span class="cm-def">state</span>) {
<span class="cm-keyword">if</span> (<span class="cm-variable-2">state</span>.<span class="cm-property">talks</span> <span class="cm-operator">!=</span> <span class="cm-keyword">this</span>.<span class="cm-property">talks</span>) {
<span class="cm-keyword">this</span>.<span class="cm-property">talkDOM</span>.<span class="cm-property">textContent</span> <span class="cm-operator">=</span> <span class="cm-string">""</span>;
<span class="cm-keyword">for</span> (<span class="cm-keyword">let</span> <span class="cm-def">talk</span> <span class="cm-keyword">of</span> <span class="cm-variable-2">state</span>.<span class="cm-property">talks</span>) {
<span class="cm-keyword">this</span>.<span class="cm-property">talkDOM</span>.<span class="cm-property">appendChild</span>(
<span class="cm-variable">renderTalk</span>(<span class="cm-variable-2">talk</span>, <span class="cm-keyword">this</span>.<span class="cm-property">dispatch</span>));
}
<span class="cm-keyword">this</span>.<span class="cm-property">talks</span> <span class="cm-operator">=</span> <span class="cm-variable-2">state</span>.<span class="cm-property">talks</span>;
}
}
}</pre>
<p>زمانی که ارائهها تغییر میکنند، این مؤلفه همهی آنها را بازترسیم مینماید. این کار ساده اما اضافی است. در قسمت تمرینها به سراغ این مشکل خواهیم رفت.</p>
<p>میتوانیم اپلیکیشن را به صورت زیر راهاندازی کنیم:</p>
<pre class="snippet cm-s-default" data-language="javascript" ><a class="c_ident" id="c_6Z6pm4dZOv" href="#c_6Z6pm4dZOv" tabindex="-1" role="presentation"></a><span class="cm-keyword">function</span> <span class="cm-def">runApp</span>() {
<span class="cm-keyword">let</span> <span class="cm-def">user</span> <span class="cm-operator">=</span> <span class="cm-variable">localStorage</span>.<span class="cm-property">getItem</span>(<span class="cm-string">"userName"</span>) <span class="cm-operator">|</span><span class="cm-operator">|</span> <span class="cm-string">"Anon"</span>;
<span class="cm-keyword">let</span> <span class="cm-def">state</span>, <span class="cm-def">app</span>;
<span class="cm-keyword">function</span> <span class="cm-def">dispatch</span>(<span class="cm-def">action</span>) {
<span class="cm-variable-2">state</span> <span class="cm-operator">=</span> <span class="cm-variable">handleAction</span>(<span class="cm-variable-2">state</span>, <span class="cm-variable-2">action</span>);
<span class="cm-variable-2">app</span>.<span class="cm-property">syncState</span>(<span class="cm-variable-2">state</span>);
}
<span class="cm-variable">pollTalks</span>(<span class="cm-def">talks</span> <span class="cm-operator">=></span> {
<span class="cm-keyword">if</span> (<span class="cm-operator">!</span><span class="cm-variable-2">app</span>) {
<span class="cm-variable-2">state</span> <span class="cm-operator">=</span> {<span class="cm-property">user</span>, <span class="cm-property">talks</span>};
<span class="cm-variable-2">app</span> <span class="cm-operator">=</span> <span class="cm-keyword">new</span> <span class="cm-variable">SkillShareApp</span>(<span class="cm-variable-2">state</span>, <span class="cm-variable-2">dispatch</span>);
<span class="cm-variable">document</span>.<span class="cm-property">body</span>.<span class="cm-property">appendChild</span>(<span class="cm-variable-2">app</span>.<span class="cm-property">dom</span>);
} <span class="cm-keyword">else</span> {
<span class="cm-variable-2">dispatch</span>({<span class="cm-property">type</span>: <span class="cm-string">"setTalks"</span>, <span class="cm-property">talks</span>});
}
}).<span class="cm-property">catch</span>(<span class="cm-variable">reportError</span>);
}
<span class="cm-variable">runApp</span>();</pre>
<p><a class="p_ident" id="p_u38iyt/PqC" href="#p_u38iyt/PqC" tabindex="-1" role="presentation"></a>اگر سرویسدهنده را اجرا کنید و دو صفحهی مرورگر را برای <a href="http://localhost:8000/"><em>http://localhost:8000</em></a> در کنار هم باز کنید، میتوانید مشاهده نمایید که کارهایی که در یک پنجره انجام میدهید در دیگر پنجره قابل مشاهده است.</p>
<h2><a class="h_ident" id="h_ggOFdVwDCk" href="#h_ggOFdVwDCk" tabindex="-1" role="presentation"></a>تمرینها</h2>
<p><a class="p_ident" id="p_TEaxbMHL6t" href="#p_TEaxbMHL6t" tabindex="-1" role="presentation"></a>تمرینهای این قسمت دربارهی ایجاد تغییر روی سیستمی است که در این فصل ایجاد شده است. برای کار روی آنها، اطمینان حاصل کنید کدهای مورد نیاز را (<a href="https://eloquentjavascript.net/code/skillsharing.zip"><em>https://eloquentjavascript.net/code/skillsharing.zip</em></a>) بارگیری کنید، Node را نصب کنید <a href="https://nodejs.org"><em>https://nodejs.org</em></a>، و وابستگیهای پروژه را نیز به وسیلهی <code>npm install</code> نصب کنید.</p>
<h3><a class="i_ident" id="i_Wc7GKnsVPe" href="#i_Wc7GKnsVPe" tabindex="-1" role="presentation"></a>مانایی دادهها در دیسک</h3>
<p>سرویسدهندهی سایت اشتراک مهارت دادههایش را کاملا در حافظه نگهداری میکند. معنای آن این است که در صورت متوقف شدن یا شروع مجدد به هر دلیلی، تمامی ارائهها و نظرات از بین خواهند رفت.</p>
<p>سرویسدهنده را توسعه دهید تا بتواند دادههای مربوط به ارائهها را روی دیسک ذخیره کرده و به صورت خودکار در صورت شروع مجدد بازیابی کند. به بهینگی فکر نکنید و سادهترین راهی که کار میکند را انتخاب کنید.</p>
<div class="solution"><div class="solution-text">
<p>سادهترین راهحلی که من میتوانم پیشنهاد دهم این است که کل شیء <code>talks</code> را به صورت JSON کدگذاری کنید و درون یک فایل به وسیلهی <code>writeFile</code> ذخیره کنید. هماکنون متدی به نام <code>updated</code> موجود میباشد که با هر بار تغییر دادههای سرویسدهنده فراخوانی میشود. میتوان آن را توسعه داد تا دادههای جدید را در دیسک ذخیره کند.</p>
<p>یک نام برای فایل انتخاب کنید مثلا <bdo><code>./talks.json</code></bdo>. در زمانی که سرویسدهنده شروع به کار میکند، میتواند آن فایل را به وسیلهی <code>readFile</code> بخواند و اگر این خواندن موفقیت آمیز بود، محتوای فایل خوانده شده را میتواند به عنوان دادههای ابتدایی استفاده کند.</p>
<p><a class="p_ident" id="p_3tGF3UDmO6" href="#p_3tGF3UDmO6" tabindex="-1" role="presentation"></a>مراقب باشید. شیء <code>talks</code> در ابتدا به عنوان یک شیء بدون prototype آغاز شد، در نتیجه میتوان از عملگر <code>in</code> با خیال راحت استفاده کرد. خروجی <bdo><code>JSON.parse</code></bdo> اشیاء معمولی با پروتوتایپ <code>Object.prototype</code> می باشد. اگر از JSON به عنوان فرمت فایلتان استفاده کنید، لازم میشود تا خاصیتهای شیئی که توسط <code>JSON.parse</code> تولید میشود را به یک شیء بدون prototype کپی کند.</p>
</div></div>
<h3><a class="i_ident" id="i_obacmbkTAm" href="#i_obacmbkTAm" tabindex="-1" role="presentation"></a>بازنشانی فیلد ورود نظرات</h3>
<p><a class="p_ident" id="p_LuaQqLANji" href="#p_LuaQqLANji" tabindex="-1" role="presentation"></a>کلیت بازترسیم ارائهها به خوبی کار میکند زیرا معمولا تفاوت بین یک گرهی DOM و جایگزین مشابهش تشخیص داده نمیشود. اما استثناهایی هم وجود دارد. اگر شروع به تایپ چیزی به عنوان یک نظر در یکی از پنجرههای مرورگر در فیلد مربوط به آن کنید، و سپس در دیگر پنجره، یک نظر به یک ارائه اضافه کنید، فیلد پنجرهی اول بازترسیم خواهد شد که در نتیجه هم محتوایش و هم focus روی آن از بین میرود.</p>
<p>در یک بحث داغ، جاییکه چندین کاربر در حال افزودن نظراتشان در یک زمان میباشند، این ایراد ممکن است آزاردهنده باشد. میتوانید راه حلی برای آن پیدا کنید؟</p>
<div class="solution"><div class="solution-text">
<p>احتمالا بهترین روش انجام اینکار این است که اشیاء مولفه برای ارائهها بسازیم، به همراه یک متد <code>syncState</code>، در نتیجه میتوان آنها را بهروز کرد تا یک نسخهی تغییریافتهی یک ارائه را نشان دهند. در زمان انجام کارهای عادی، تنها راهی که یک ارائه میتواند تغییر کند اضافه کردن نظرات بیشتر است، پس متد <code>syncState</code> میتواند نسبتا ساده باشد.</p>
<p>بخش مشکل ماجرا این است که، زمانی که یک لیست تغییر یافته از ارائهها میآید، ما باید لیست مؤلفههای موجود DOM را با ارائههای موجود در لیست جدید یکپارچه کنیم- با حذف مؤلفههایی که ارائههایش حذف شده است و بهروزرسانی مؤلفههایی که ارائهش تغییر یافته است.</p>
<p>برای انجام این کار، شاید مفید باشد که ساختار دادهای داشته باشیم که مؤلفههای ارائه را تحت عنوانهای ارائه ذخیره کند در نتیجه میتوان به سادگی وجود یک مؤلفه برای ارائهی داده شده را بررسی کرد. بعدا میتوانید آرایهی جدید ارائهها را پیمایش کرده و برای هر یک از آنها، یا یک مؤلفهی موجود را هماهنگ کنید یا مؤلفهی جدید را بسازید. برای حذف مؤلفهها برای ارائههایی که حذف شده اند، لازم است همچنین مؤلفهها را پیمایش کنید و تا ببینید ارائههای مربوط هنوز موجود هستند یا خیر.</p>
</div></div><nav><a href="20_node.html" title="previous chapter">◀</a> <a href="index.html" title="cover">◆</a></nav>
</article>