Slide 1

Slide 1 text

.JOJNVN)BOETPO/PEFKT ձ৔ 3PPN# ೔࣌ 2019೥12݄01೔ 14:15ʙ16:00 ్த15෼ͷٳܜΛڬΈ·͢ ϋογϡλά #jsconfjp_b εϐʔΧʔ ɹɹɹ܀ࢁଠرʢ!"KJEPʣ ɹɹɹϠϑʔגࣜձࣾ›/PEFKTࠇଳ

Slide 2

Slide 2 text

.*/*.6.)"/%40//0%&+4 / 53 ௌߨର৅ऀ▶︎+BWB4DSJQUͳΒগ͠Θ͔Δ͚ΕͲ/PEFKT͸͍·ͻͱͭͱ͍͏ํ ಘΒΕΔ෺▶︎ࠓ͙͢࢖͑Δ/PEFKTͷجૅ஌ࣝ ͜ͷࢿྉͷϦϯΫ https://speakerdeck.com/ajido/minimum-handson-nodejs ԋशϦϯΫ https://chouseisan.com/s?h=4a0d7eb9c38a440e9ad585abfbb16e79 ‣ bit.ly/2XWSQQK εΫϦϓτϦϯΫ https://github.com/ajido/nodejs-handson ϋϯζΦϯதʹ্هϦϯΫϖʔδΛར༻͠·͢ɻࣄલʹ֬͝ೝ͍ͩ͘͞ ! 2

Slide 3

Slide 3 text

+BWB4DSJQU &4 &4ʙ 5ZQF4DSJQU ֶशϚοϓ ηΩϡΞϓϩάϥϛϯά / 53 04 104*9 8)"58("1* %0. FUD 'SPOUFOE 'SBNFXPSLT 8FC 'SBNFXPSLT 1BB4 $BB4 'BB4 /PEFKT$PSF"1* )551)5514 5$1*1 ˔ࠓճͷϋϯζΦϯ಺༰ 5PPMT %# 5FTU -PHFUD ! 3

Slide 4

Slide 4 text

جૅ Ԡ༻ ‣ Πϯετʔϧ 5 ‣ ϑϩʔ੍ޚ 38 ‣ Ϟδϡʔϧ؅ཧ 11 ‣ ඇಉظؔ਺ 42 ‣ ಛ௃ 16 ‣ σόοά ‣ σβΠϯύλʔϯ 21 ‣ Ϟδϡʔϧͷ࡞੒ ‣ ίʔϧόοΫ 23 ‣ ϓϩϑΝΠϦϯά ‣ Πϕϯτۦಈ 31 ‣ ϑϨʔϜϫʔΫͱπʔϧ ‣ ಉظॲཧ 35

Slide 5

Slide 5 text

Πϯετʔϧ OPEFKTPSH͔ΒΠϯετʔϧ͠·͢ɻ-54ͱ$VSSFOUͷ͏ͪɺ௕ظؒͷϝϯςφϯ ε͕อূ͞ΕɺՄೳͳݶΓޓ׵ੑ͕ҡ࣋͞ΕΔ-54όʔδϣϯΛબ୒͍ͯͩ͘͠͞ɻ ฐࣾͰ͸ιʔείʔυϏϧυͯ͠31.ύο έʔδʹ͍ͯ͠·͢ɻͦͷࡍύοέʔδϨ δετϦΛࣾ಺ʹ޲͚ͯηΩϡϦςΟରࡦ ͕ߦ͑ΔΑ͏ʹ͍ͯ͠·͢ɻ -54όʔδϣϯΛબ୒ IUUQTOPEFKTPSH -54 ۮ਺ ϲ݄ $VSSFOU ح਺ ϲ݄ ! 5 / 53

Slide 6

Slide 6 text

දࣔͰ͖ͨΒɺ  ԋशϦϯΫͷϖʔδΛ։͘  ʮग़ܽΛೖྗ͢ΔʯΛԡ͢  ࠓճ͸ԋशςετͳͷͰʮԋशςετʯͷ߲໨Λº͔Β⚪ʹมߋ͢Δ  ʮೖྗ͢ΔʯΛԡͯ͠ɺԋशΛ׬ྃ͢Δ  λʔϛφϧͰ/PEFKTͷόʔδϣϯΛද͍ࣔͯͩ͘͠͞ɻ $ node -v v12.13.1 ԋशςετ ԋशϦϯΫ https://chouseisan.com/s?h=4a0d7eb9c38a440e9ad585abfbb16e79 ‣ bit.ly/2XWSQQK ͜ͷࢿྉͷϦϯΫ https://speakerdeck.com/ajido/minimum-handson-nodejs ! 6 / 53

Slide 7

Slide 7 text

ඪ४Ϟδϡʔϧʢ$PSF"1*ʣ /PEFKT͸(PPHMF$ISPNFʹ࠾༻͞Ε͍ͯΔ7+BWB4DSJQUΤϯδϯͱɺඪ४ͷ +BWB4DSJQUʹ͸ͳ͍֤छ࣮૷͕૊Έ߹ΘͤΒΕͨ+BWB4DSJQUͷ࣮ߦ؀ڥͰ͢ɻ const fs = require(‘fs') fs.readFile(__filename, (err, data) => { console.log(data) }) ͜͜Ͱͷඪ४Ϟδϡʔϧͱ͸ɺ/PEFKTʹ૊Έࠐ·Εͨʮඪ४ͷ+BWB4DSJQUʹ͸ͳ͍ ֤छ࣮૷ʯͷ͜ͱΛࢦ͠·͢ɻ __filename͸ࣗ਎ͷεΫϦϓτ ϑΝΠϧͷઈରύεͰ͢ɻ ! 7 / 53

Slide 8

Slide 8 text

ඪ४Ϟδϡʔϧʢ$PSF"1*ʣ fs ϑΝΠϧΛ࡞੒ɾ࡟আ͢Δ path ϑΝΠϧύεɾσΟϨΫτϦύεΛѻ͏ http )551αʔόɾΫϥΠΞϯτΛ࣮૷͢Δ crypto ϋογϡɾ҉߸ɾॺ໊ɾݕূͳͲͷ҉߸ػೳΛѻ͏ util ϢʔςΟϦςΟϞδϡʔϧ querystring 63-ΫΤϦจࣈྻΛύʔε͢Δ os $16ͷ਺΍ϗετ໊ͳͲͷ04ؔ࿈৘ใΛࢀর͢Δ child_process ࢠϓϩηεΛ࡞੒͢Δ cluster αʔόΛϚϧνϓϩηεԽ͢Δ assert ม਺ͷ಺༰΍ॲཧͷԠ౴ΛνΣοΫ͢Δ ΧςΰϦ جຊతͰଞʹґଘੑͷͳ͍"1* Events, Modules, Buffer, Stream ΧςΰϦ /PEFݻ༗ͷ΋ͷͰຊମͷಈ࡞ʹؔ࿈͢Δ"1* Timers, Process, Console ΧςΰϦ 04ͷػೳ΍ଞͷϥΠϒϥϦͱؔ࿈͢Δ"1* File System, Net, UDP/Datagram, TLS/SSL, Child Processes ΧςΰϦ ΞϓϦέʔγϣϯ޲͚ͷԠ༻"1* HTTP, HTTPS, Cluster IUUQTOPEFKTPSHEJTUMBUFTUEPDTBQJEPDVNFOUBUJPOIUNM ֤छඪ४Ϟδϡʔϧͱͦͷ༻్ͷҰྫ جఈΦϒδΣΫτผʹΧςΰϥΠζͨ͠ਤɻ ΧςΰϦ͔Βॱʹֶश͢Δ͜ͱͰɺΑΓཧղ͕ਂ·Γ·͢ ! 8 / 53

Slide 9

Slide 9 text

3&1- /PEFKTͷର࿩తΠϯλʔϑΣʔεͰ͢ɻ3&1-ʢ3FBE&WBM1SJOU-PPQʣ؀ڥͰ ͸ඪ४Ϟδϡʔϧ͕ࣗಈతʹϩʔυ͞ΕΔͨΊɺͦΕΒΛಡΈࠐΉ͜ͱͳ͘ѻ͑·͢ɻ $ node > /^\d+$/.test(‘1024’) true // requireすることなく標準モジュールを呼び出せます > os.hostname() ‘MAC-Z-941.local’ > .exit ίʔυͷ͢͹΍͍ݕূʹ໾ཱͪ·͢ɻԋशத΋ੋඇ׆༻ͯ͠Έ͍ͯͩ͘͞ɻ 3&1-ͷऴྃ͸-D ΛૹΔ͔ .exit ͱೖྗ͍ͯͩ͘͠͞ɻ ͦͷଞίϚϯυ͸.helpΛࢀর ! 9 / 53

Slide 10

Slide 10 text

 ࣗ਎ͷ୺຤ͷ$16ίΞͷ਺Λɺඪ४Ϟδϡʔϧͱ3&1-Λ࢖ͬͯද͍ࣔͯͩ͘͠͞ɻ $ node > 䡧䡧䡧䡧䡧䡧䡧䡧䡧 [ { model: 'Intel(R) Core(TM) i5-5287U CPU @ 2.90GHz', speed: 2900, times: { user: 78882290, nice: 0, sys: 63998990, idle: 611174200, irq: 0 } }, ... ] > 䡧䡧䡧䡧䡧䡧䡧䡧䡧䡧䡧䡧䡧䡧䡧䡧 4 ԋश ͜ͷࢿྉͷϦϯΫ https://speakerdeck.com/ajido/minimum-handson-nodejs ԋशϦϯΫ https://chouseisan.com/s?h=4a0d7eb9c38a440e9ad585abfbb16e79 ‣ bit.ly/2XWSQQK εΫϦϓτϦϯΫ https://github.com/ajido/nodejs-handson 3&1-ͷऴྃ͸-D ΛૹΔ͔ .exit ͱೖྗ͍ͯͩ͘͠͞ɻ ͦͷଞίϚϯυ͸.helpΛࢀর ! 10 / 53

Slide 11

Slide 11 text

جૅ›Ϟδϡʔϧ؅ཧ

Slide 12

Slide 12 text

OQN OQN /PEF1BDLBHF.BOBHFS ͸OQN *ODʹΑΓ؅ཧɾఏڙ͞Ε͍ͯΔ/PEFKT૊Έ ࠐΈͷύοέʔδ؅ཧγεςϜͰ͢ɻੈքத͔Βར༻͞ΕΔதԝϨδετϦͷOQNKTDPNʹ ͸ଟ਺ͷϞδϡʔϧ͕ެ։͞Ε͍ͯ·͢ɻ ಺༰ όʔδϣϯ؅ཧ node_modules Ϟδϡʔϧͷ࣮ମɾιʔείʔυ package-lock.json Πϯετʔϧ͞ΕͨϞδϡʔϧͷπϦʔ৘ใ ✔ package.json ύοέʔδͷϝλσʔλ ϓϩδΣΫτʹ௚઀ґଘ͢ΔϞδϡʔϧͷ؅ཧσʔλͳͲ ✔ ۮൃతͳύοέʔδͷެ։Λ๷͙ͨΊʹ package.jsonʹprivate:trueΛ௥هͯ͠ ͍ͩ͘͞ɻ ͜ͷΦϓγϣϯΛ༗ޮʹ͢Δ͜ͱͰɺຊདྷ package.jsonʹهड़͠ͳ͚Ε͹ͳΒͳ͍֤छ ৘ใΛলུ͢Δ͜ͱ΋Ͱ͖·͢ɻ package-lock.json͕͋Ε͹node_modules σΟϨΫτϦ͸෮ݩͰ͖·͢ɻnode_modules͸ όʔδϣϯ؅ཧͷର৅͔Β֎͍ͯͩ͘͠͞ɻ ! 12 / 53 // プロジェクトを新規作成する $ npm init $ npm install request $ ls ├── node_modules ├── package.json └── package-lock.json

Slide 13

Slide 13 text

 npminitίϚϯυͰpackage.jsonΛ࡞੒ͨ͋͠ͱrequestϞδϡʔϧΛΠϯε τʔϧ͍ͯͩ͘͠͞ɻͦͷޙrequestϞδϡʔϧΛ࢖ͬͯ)551ϦΫΤετΛૹΓɺ εςʔλείʔυΛද͍ࣔͯͩ͘͠͞ɻ const request = require('request') request('https://www.yahoo.co.jp', (err, res, body) => { console.log(res.statusCode) }) ԋश ԋशϦϯΫ https://chouseisan.com/s?h=4a0d7eb9c38a440e9ad585abfbb16e79 ‣ bit.ly/2XWSQQK εΫϦϓτϦϯΫ https://github.com/ajido/nodejs-handson $ npm init $ npm install request ! 13 / 53

Slide 14

Slide 14 text

ηϚϯςΟοΫόʔδϣχϯά ."+03 ޙํޓ׵ੑΛࣦ͏ͱ͖ʹΠϯΫϦϝϯτ͞ΕΔ ͦͷࡍ.*/03ͱ1"5$)Λʹ͢Δ .*/03 ޙํޓ׵ੑ͕͋ΓɺػೳΛ௥Ճͨ͠ͱ͖ʹΠϯΫϦϝϯτ͞ΕΔ ͦͷࡍ1"5$)Λʹ͢Δ 1"5$) ޙํޓ׵ੑ͕͋ΓɺόάΛमਖ਼ͨ͠ͱ͖ʹΠϯΫϦϝϯτ͞ΕΔ ? ."+03.*/031"5$) ?ه߸͸ύοέʔδͷόʔδϣϯൣғΛࢦఆ͓ͯ͠Γɺࠨଆͷ ஋Ͱ͸ͳ͍਺஋Λݻఆ͢Δ͜ͱΛද͍ͯ͠·͢ɻ͜Ε͸ഁյతͳ มߋΛආ͚ͨ߹ཧతͳΞοϓσʔτΛߦ͏͜ͱΛҙຯ͠·͢ɻ OQNύοέʔδ͸͜ͷ࢓༷ ʹ४ڌͨ͠όʔδϣϯ؅ཧ͕ ਪ঑͞Ε͍ͯ·͢ɻ ύοέʔδར༻ऀ΋جຊతʹ ͸ηϚϯςΟοΫόʔδϣχ ϯάΛ೦಄ʹύοέʔδΛ؅ ཧ͍ͯͩ͘͠͞ɻ ! 14 / 53

Slide 15

Slide 15 text

Ϟδϡʔϧ؅ཧϫʔΫϑϩʔ $ npm init // 必要なら private: true を追加 $ npm install // ツリー情報から node_modules を再構築 $ npm ci $ npm audit $ npm audit fix $ npm outdated $ npm update / npm install @ ϓϩδΣΫτΛ৽ن࡞੒͢Δ৔߹ طଘͷϓϩδΣΫτʹՃΘΔ৔߹ ੬ऑੑνΣοΫ ύοέʔδͷߋ৽νΣοΫɾ௥Ճ ! 15 / 53

Slide 16

Slide 16 text

جૅ›ಛ௃

Slide 17

Slide 17 text

ಛ௃ͱ஫ҙ఺ /PEFKTͷαʔόʔ͸γϯάϧϓϩηεɾγϯάϧεϨουͰେྔͷϦΫΤετΛॲཧ͠·͢ɻ͜ ͷ؍఺͔Βɺ·ͣ͸04ͷϓϩηε͋ͨΓͷϑΝΠϧσΟεΫϦϓλ਺ͷ੍ݶʹ஫ҙ͍ͯͩ͘͠͞ɻ # 初期値では明らかに足りません $ ulimit -n 1024 … [Service] LimitNOFILE=65536 ॳظ஋ͷ··ϓϩμΫγϣϯ؀ڥͰϦΫΤετΛड͚෇͚ͯ͠·͏ͱɺߴ͍τϥϑΟοΫΛॲཧ ࢝͠ΊͨࡍʹToo many open filesʹΑΔϓϩηεμ΢ϯ͕΄΅࣮֬ʹൃੜ͠·͢ɻ $ ulimit -n 65536 $ ulimit -n 65536 ulimit -nʹΑΔ੍ݶͷ্ॻ͖ TZTUFNEʹΑΔ੍ݶͷઃఆ ัଊϚϧνίΞ͕׆༻Ͱ͖Δ؀ڥͰ͸Ұൠ తʹclusterϞδϡʔϧΛ࢖ͬͯϚϧνϓ ϩηεԽ͠·͢ɻ ! 17 / 53

Slide 18

Slide 18 text

Πϕϯτϧʔϓ /PEFKT͕େྔͷτϥϑΟοΫΛॲཧ͠ଓ͚Δ؀ڥΛҡ࣋͢ΔͨΊʹ͸ɺΠϕϯτϧʔϓΛՄೳ ͳݶΓࢭΊͣʹӡ༻͢Δ͜ͱ͕΋ͬͱ΋ॏཁͰ͢ɻ͜ͷΠϕϯτϧʔϓ͕ͳʹ͔Λઆ໌͢Δલ ʹɺ·ͣ͸Πϕϯτϧʔϓ͕؆୯ʹࢭ·ͬͯ͠·͏࣮૷ྫΛྻڍ͠·͢ɻ ϑΝΠϧ΍ωοτϫʔΫॲཧΛ൐Θͳ͍$16Λ௕࣌ؒ઎༗͠ଓ͚Δॲཧͱɺ଴ػ࣌ؒͷ௕͍ಉظతͳ "1*Λར༻͢Δ͜ͱͰɺΠϕϯτϧʔϓ͸க໋తͳ·Ͱʹఀࢭ͠·͢ɻ ͋Γ͕ͪ౓ ΠϕϯτϧʔϓΛ௕࣌ؒఀࢭͤ͞Δ࣮૷ͷҰྫ ⚠⚠⚠ ਺ඦ,#Ҏ্ͷڊେͳ+40/σʔλΛॲཧ͢Δ ⚠⚠ GTϞδϡʔϧͷಉظؔ਺Λ࢖͏ ⚠ ϧʔϓΛճ͠ଓ͚Δ ܦݧ্ɺڊେͳ+40/ͷύʔεʹΑΔ ύϑΥʔϚϯεྼԽ͕ඇৗʹଟ͍ɻ ! 18 / 53

Slide 19

Slide 19 text

Πϕϯτϧʔϓ const http = require('http') http.createServer((req, res) => { // この部分の記述次第でパフォーマンスが劇的に変化する res.end() }).listen(8080) Πϕϯτϧʔϓ͸/PEFKTʹ͓͚Δ಄೴ͷΑ͏ͳ΋ͷͰɺ༩͑ΒΕ໋ͨྩΛॱ࣮࣍ߦ͢ ΔίΞͰ͢ɻ͜ΕΛҰ੾ఀࢭͤ͞ͳ͍ͱ͍͏͜ͱ͸ෆՄೳͰ͕͢ɺఀࢭ࣌ؒΛஅଓత͔ ͭ࠷খݶʹ཈͑ଓ͚Δ͜ͱͰɺ/PEFKT͸εϜʔζʹϦΫΤετΛॲཧ͠ଓ͚Δ͜ͱ͕ Ͱ͖·͢ɻ $ vegeta attack -duration=10s -rate=0 -max-workers=100 ॲཧͷ಺༰ 314 ͳ͠  JSON.parse 20KB  JSON.parse 500KB  fs.readFile 1MB  fs.readFileSync 1MB  forEach Loop 100  forEach Loop 100,000  ! 19 / 53

Slide 20

Slide 20 text

+40/σʔλͷ࠷దͳαΠζ͸ʁ ୯७ͳߏ଄ͷ+40/σʔλΛύʔεͨ͠ͱ͖ͷΠϕϯτϧʔϓͷఀࢭ࣌ؒ 0ms 50ms 100ms 150ms 200ms 100KB 300KB 500KB 700KB 900KB 2M 4M 6M 8M 10M 30M 50M ࠷దͳ஋͸༩͑ΒΕΔσʔλ΍ΞϓϦέʔγϣϯʹԠ࣮ͨ͡ࢪࢼݧʹΑׂͬͯΓग़͢΂͖Ͱ͕͢ɺ ໨҆ͱͯ͠͸ʮʢΑ΄Ͳେ͖ͯ͘΋ʣ࠷େ.ʹऩ·Δʯ͜ͱΛ೦಄ʹʮ࣌ؒܦա΍γεςϜͷ੒௕ʹ ൐͍+40/ͷαΠζ͕ංେԽ͠ͳ͍ʯ͜ͱͱʮϚϧνϓϩηεԽ͢Δʯ͜ͱʹཹҙ͍ͯͩ͘͠͞ɻ ! 20 / 53 ‑ڐ༰ൣғ ‑ݶք

Slide 21

Slide 21 text

σβΠϯύλʔϯ

Slide 22

Slide 22 text

σβΠϯύλʔϯ /PEFKT͸ΠϕϯτϧʔϓΛఀࢭͤ͞ΔՄೳੑ͕͋Δॲཧͷେ෦෼Λඇಉظॲཧͱ͢Δ ͜ͱͰɺޮ཰తʹτϥϑΟοΫΛॲཧ͠·͢ɻ͜ͷಛੑʹج͍ͨ/PEFKTͷಛ௃త͔ͭ جૅతͳೋͭͷσβΠϯύλʔϯΛֶͼ·͢ɻ ! 22 / 53

Slide 23

Slide 23 text

σβΠϯύλʔϯ›ίʔϧόοΫ

Slide 24

Slide 24 text

ίʔϧόοΫ Լه͸ͲͪΒ΋ϑΝΠϧΛಡΈࠐΈग़ྗ͍ͯ͠·͢ɻϑΝΠϧͷಡΈࠐΈʹfs.readFileSyncΛར ༻ͨ͠৔߹͸ಉظతͳॲཧͱͳΓɺಡΈࠐΈ׬ྃ·ͰΠϕϯτϧʔϓΛࢭΊͯ͠·͍·͕͢ɺ fs.readFileΛར༻͢ΔͱඇಉظతͳॲཧͱͳΓɺಡΈࠐΈ׬ྃ·ͰͷؒɺଞͷॲཧʹϦιʔεΛ ׂΓ౰ͯΔ͜ͱ͕Ͱ͖·͢ɻ // 非同期 … 読み込み中イベントループが停止しない。完了後にコールバック関数が呼び出される const fs = require(‘fs') fs.readFile(__filename, (err, data) => { console.log(data.toString()) }) // 同期 … ファイル読み込み中イベントループが停止する const fs = require(‘fs') const data = fs.readFileSync(__filename).toString() console.log(data) ! 24 / 53

Slide 25

Slide 25 text

ίʔϧόοΫͷ׳ྫ ίʔϧόοΫؔ਺͸جຊతʹෳ਺ճݺͼग़͞ΕΔ͜ͱ͸͋Γ·ͤΜɻྫ֎͸͋Γ·͕͢ɺඇ ಉظॲཧͷ׬ྃޙʹҰ౓͚ͩݺͼग़͞Ε·͢ɻͦͯ͠ݺͼग़͞ΕͨίʔϧόοΫؔ਺ͷୈҰ Ҿ਺ʹ͸ɺॲཧ͕੒ޭ͔ͨ͠Ͳ͏͔Λද͢ΤϥʔΦϒδΣΫτ͕ฦΓ·͢ɻ ίʔϧόοΫύλʔϯͷ׳ྫ "1*ͷ࠷ޙͷҾ਺͕ίʔϧόοΫؔ਺ fs.readFile(path[, options], callback) ॲཧͷ׬ྃޙʹίʔϧόοΫؔ਺͕Ұ౓͚ͩݺͼग़͞ΕΔ ݺͼग़͞ΕͨίʔϧόοΫؔ਺ͷୈҰҾ਺͕ඇಉظॲཧͷࣦഊΛද͢ΤϥʔΦϒδΣΫτ fs.readFile(__filename, (err, data) => { console.log(data.toString()) }) ! 25 / 53

Slide 26

Slide 26 text

ίʔϧόοΫͷΤϥʔϋϯυϦϯά ίʔϧόοΫؔ਺ͷୈҰҾ਺͕ΤϥʔΦϒδΣΫτͰ͢ɻΤϥʔϋϯυϦϯά͸ୈҰҾ਺ ͷΤϥʔΦϒδΣΫτͷOVMMνΣοΫΛߦ͍ɺOVMMͰͳ͍৔߹͸ΤϥʔΛॲཧͯ͘͠ ͍ͩ͞ɻ const fs = require('fs') fs.readFile(__filename, (err, data) => { if (err) { console.error(err) return } console.log(data) }) Τϥʔͷதʹ͸ແࢹ͢ΔʢʹϋϯυϦϯά ͠ͳ͍ʣ͜ͱͰϓϩηε͕Ϋϥογϡͯ͠ ͠·͏΋ͷ΋ଘࡏ͠·͕͢ɺίʔϧόοΫ ୈҰҾ਺ͷΤϥʔ͸ແࢹͯ͠΋ϓϩηε͕ Ϋϥογϡ͢Δ͜ͱ͸͋Γ·ͤΜɻ ! 26 / 53

Slide 27

Slide 27 text

ίʔϧόοΫͷΑ͋͘Δࣦഊྫ returnͷॻ͖๨Ε // 誤 const fs = require('fs') fs.readFile(__filename, (err, data) => { if (err) console.error(err) console.log(data) }) // 正 const fs = require('fs'); fs.readFile(__filename, (err, data) => { if (err) return console.error(err) console.log(data) }) ! 27 / 53 ҟৗܥͷॲཧͷ͋ͱਖ਼ৗܥͷॲཧ΋ ૸ͬͯ͠·͍·͢ɻ

Slide 28

Slide 28 text

ίʔϧόοΫͷΑ͋͘Δࣦഊྫ try/catchΛ࢖ͬͯඇಉظॲཧͷΤϥʔΛั·͑Δ // 誤 const fs = require('fs') try { fs.readFile(__filename, (err, data) => { console.log(data) }) } catch (err) { console.error(err) } // 正 const fs = require('fs') fs.readFile(__filename, (err, data) => { if (err) return console.error(err) console.log(data) }) ͨͩ͠ඇಉظॲཧͷલஈ֊ͷΤϥʔ͸͜ͷtry/catchʹΑͬͯิ଍Ͱ͖·͢ɻ ͦͷͨΊ͜ͷɹ try/catch΋ݫີʹ͸ඞཁͱݴ͑Δ͜ͱʹ஫ҙ͍ͯͩ͘͠͞ɻ ! 28 / 53

Slide 29

Slide 29 text

ίʔϧόοΫͷΑ͋͘Δࣦഊྫ ඇಉظॲཧͷಉظతͳϧʔϓ // 誤 const fs = require('fs') for (let i = 0; i < 100; i++) { fs.appendFile('./data.txt', i + ',', (err) => { }) } // 0,3,4,5,6,7,8,9,10,11,12,20,21,1,2,28,3,4,5,6,7,8... // 再帰処理による対処の一例(必ずしも正解というわけではありません) const write = (i) => { if (i === 100) return fs.appendFile('./data.txt', i + ',', (err) => { write(++i) }) } write(0) 0-99ͷ਺஋ΛॱʹϑΝΠϧʹॻ͖ࠐΉ͜ͱΛҙ ਤͯ͠ϧʔϓ͍ͯ͠·͕͢ɺ࣮ࡍ͸ʮ΄΅ಉ࣌ʹ 0-99ͷ਺஋ΛϑΝΠϧʹॻ͖ࠐΉʯॲཧʹͳͬ ͍ͯ·͢ɻ͜ͷͨΊ݁Ռ͸ॱෆಉͳ਺஋ͷฒͼʹ ͳΓ·͢ɻ ! 29 / 53

Slide 30

Slide 30 text

σβΠϯύλʔϯ›Πϕϯτۦಈ

Slide 31

Slide 31 text

Πϕϯτۦಈ ඇಉظॲཧͱͷ਌࿨ੑ͕ߴ͍΋͏ͻͱͭͷσβΠϯύ λʔϯ͕ΠϕϯτۦಈͰ͢ɻ/PEFKTͷΠϕϯτۦ ಈύλʔϯ͸EventEmitterͱ͍͏جఈΫϥεΛ ೚ҙͷΦϒδΣΫτʹܧঝͤ͞Δ͜ͱͰ࣮૷͠·͢ɻ const Redis = require('ioredis') const redis = new Redis() const stream = redis.scanStream({ match: '*', count: 100 }) // 約100件のキーを読み込むごとに呼び出される stream.on('data', (keys) => { for (const key of keys) { console.log(key) } }) // 全てのキーを走査した後に呼び出される stream.on('end', () => { console.log(‘end') }) ,74ʹ઀ଓͯ͠ΩʔͷҰཡΛྻڍ͢ΔΠϕϯτۦಈܕͷίʔυ EventEmitter Λܧঝͨ͠ ΠϕϯτۦಈܕͷΦϒδΣΫτ ίʔϧόοΫͱͷҧ͍ ॲཧͷ׬ྃʹݶΒͣɺॲཧͷ్தɾ։࢝ɾऴྃͳͲɺ ঢ়ଶͷมԽΛද༷͢ʑͳλΠϛϯάͰॲཧΛڬΈࠐΉ͜ͱ͕Ͱ͖Δ ঢ়ଶͷมԽΛද͢ʮΠϕϯτʯ͸Կ౓΋܁Γฦ͠ൃੜ͠ɺ ͦͷ౓ʹରԠ͢ΔϦεφʔؔ਺͕ݺͼग़͞ΕΔ ,74ʹ਺͑੾Εͳ͍ྔͷΩʔ͕ଘࡏ͍ͯͨ͠ͱͯ͠΋ɺ݅୯ҐͰॲཧ ͍ͯ͠ΔͨΊɺϝϞϦΛѹഭ͢Δ͜ͱͳ͘ɺΠϕϯτϧʔϓΛ௕࣌ؒఀࢭ͞ ͤΔ͜ͱ΋͋Γ·ͤΜɻ ! 31 / 53

Slide 32

Slide 32 text

ΠϕϯτۦಈͷΤϥʔϋϯυϦϯά ΠϕϯτۦಈܕͷΠϯλʔϑΣʔεΛ࣋ͬͨΦϒδΣ ΫτͷΤϥʔΠϕϯτʹϦεφʔؔ਺Λ௥Ճ͠·͢ɻ const Redis = require('ioredis') const redis = new Redis() const stream = redis.scanStream({ match: '*', count: 100 }) // 約100件のキーを読み込むごとに呼び出される stream.on('data', (keys) => { for (const key of keys) { console.log(key) } }) // 全てのキーを走査した後に呼び出される stream.on('end', () => { console.log(‘end') }) // 何かしらエラーが発生した時に呼び出される stream.on('error', (err) => { console.error(err) }) ΠϕϯτۦಈܕͷΦϒδΣΫτΛݟ෼͚Δώϯτ ˞ඞͣ͠΋੒ཱ͢Δ৚݅Ͱ͸͋Γ·ͤΜ υΩϡϝϯτʹൃੜ͢ΔΠϕϯτ͕هࡌ͞Ε͍ͯΔ .onϝιου͕ଘࡏ͢Δ streamͱ͍͏໊শ͕࢖ΘΕ͍ͯΔ ΦϒδΣΫτ͕Πϕϯτۦಈܕ͔Ͳ͏͔͸ɺओʹυΩϡϝϯτ͔Β൑அ͠· ͢ɻͦͯ͠ΠϕϯτۦಈܕͷΠϯλʔϑΣʔεͰ͋ͬͨ৔߹͸on(error) Λ࢖ͬͯΤϥʔॲཧΛ௥Ճ͍ͯͩ͘͠͞ɻ emitter.on('error', (err) => { console.error(err) }) ! 32 / 53

Slide 33

Slide 33 text

ΠϕϯτۦಈͷΑ͋͘Δࣦഊྫ on('error')ͷϋϯυϦϯά࿙ΕͱͦΕʹ൐͏ϓϩηεͷΫϥογϡ // 誤 const fs = require('fs') const rs = fs.createReadStream(__filename) rs.on('data', (data) => { console.log(data) }) // 正 const fs = require('fs') const rs = fs.createReadStream(__filename) rs.on('data', (data) => { console.log(data) }) rs.on('error', (err) => { console.error(err) }) ΠϕϯτۦಈܕͷΤϥʔϋϯυϦϯά͸୭΋͕๨Ε͕ͪͰ͢ɻϋϯυϦϯάΛ๨ΕΔͱΤϥʔ ൃੜ࣌ʹϓϩηε͕Ϋϥογϡͯ͠͠·͏ͨΊɺࡉ৺ͷ஫ҙΛ෷͏ඞཁ͕͋Γ·͢ɻ ! 33 / 53

Slide 34

Slide 34 text

ಉظॲཧ

Slide 35

Slide 35 text

ಉظॲཧͱඇಉظॲཧΛݟ෼͚Δʹ͸ʁ const data = Buffer.alloc(1024 * 1024 * 1024) data.forEach((value) => { console.log(value) }) ͜Ε·Ͱඇಉظॲཧʹదͨ͠σβΠϯύλʔϯͱɺͦΕʹରԠ͢ΔΤϥʔϋϯυϦϯάΛղઆ ͠·͕ͨ͠ɺ࠷ޙʹಉظॲཧʹ͍ͭͯ঺հ͠·͢ɻ·ͣಉظॲཧͱඇಉظॲཧΛݟ෼͚Δํ๏ Ͱ͕͢ɺද໘తͳ৘ใ͔Β࣮֬ʹ൑அ͢Δํ๏͸͋Γ·ͤΜɻͰ͕͢ҎԼʹώϯτͱͳΔ৘ใ Λྻڍ͠·͢ɻ ඇಉظॲཧΛݟ෼͚Δώϯτ˞ඞͣ͠΋੒ཱ͢Δ৚݅Ͱ͸͋Γ·ͤΜ ωοτϫʔΫ·ͨ͸ϑΝΠϧΛѻ͏ "1*ͷ࠷ޙͷҾ਺͕callbackͰ͋Γɺ"1*ͷ໊শʹSync͕෇͍͍ͯͳ͍ 1SPNJTFΦϒδΣΫτΛฦ͢ ! 35 / 53 ίʔϧόοΫؔ਺Λड͚औΔ"1*Ͱ͕͢ ಉظॲཧͰ͢ɻίʔϧόοΫʹඇಉظॲ ཧͱ͍͏Θ͚Ͱ͸͋Γ·ͤΜ

Slide 36

Slide 36 text

ಉظॲཧͷΤϥʔϋϯυϦϯά ಉظॲཧ͸্͔ΒॱʹίʔυΛ࣮ߦͯ͠ɺ࣮ߦதͷॲཧ͕׬ྃ͢Δ·Ͱ࣍ͷॲཧͷ։࢝ ΛϒϩοΫ͢ΔҰൠతͰ௚ײతͳૢ࡞Ͱ͢ɻ͜ͷಉظॲཧͷΤϥʔϋϯυϦϯά͸ɺಉ ظॲཧΛtry/catchͰғ͏͜ͱʹΑͬͯߦ͍·͢ɻ try { // JSON.parseの例外処理は特に忘れがちなので注意してください const data = JSON.parse('invalid_json') } catch (err) { console.error(err) } try { const data = fs.readFileSync('invalid_path') } catch (err) { console.error(err) } fs.readFileSync΍thrownewError()΋try/ catchΛ࢖ͬͯΤϥʔΛॲཧ͠·͢ɻಉظॲཧͷΤϥʔ͸ ϋϯυϦϯά͕࿙Ε͍ͯΔͱϓϩηε͕Ϋϥογϡ͢Δͨ Ίɺे෼ʹ஫ҙ͍ͯͩ͘͠͞ɻ ! 36 / 53

Slide 37

Slide 37 text

νΣοΫϙΠϯτ ‣ /PEFKT͸-54όʔδϣϯΛར༻͢Δ ‣ Ϟδϡʔϧͷར༻ͱ؅ཧํ๏Λཧղ͢Δ ‣ ඇಉظॲཧΛۦ࢖ͯ͠Πϕϯτϧʔϓͷఀࢭ࣌ؒΛ࠷খݶʹ཈͑Δ ‣ ίʔϧόοΫͱΠϕϯτۦಈͷσβΠϯΛཧղ͢Δ ‣ ίʔϧόοΫͱΠϕϯτۦಈͷΤϥʔϋϯυϦϯάํ๏Λཧղ͢Δ ‣ ಉظॲཧͷΤϥʔϋϯυϦϯάํ๏Λཧղ͢Δ ઃܭύλʔϯ ΤϥʔϋϯυϦϯά Ϋϥογϡ ඇಉظॲཧ $BMMCBDL if (err) &WFOU&NJUUFS emitter.on(‘error') ✔ ಉظॲཧ try/catch  ✔ ! 37 / 53

Slide 38

Slide 38 text

ϑϩʔ੍ޚ

Slide 39

Slide 39 text

ϑϩʔ੍ޚ app.get(‘/upload', (req, res) => { // 1. データベースに問い合わせてユーザー情報を取得する非同期処理 // 2. オブジェクトストレージにファイルをアップロードする非同期処理 // 3. データベースにメタ情報を保存する非同期処理 res.status(200).end(data) } 8FC"1*ͷ։ൃͰ͸ԼهͷΑ͏ʹ௚ྻͷॲཧʢલͷॲཧ͕ऴ͔ྃͯ͠Β࣍ͷॲཧΛߦ͏ʣΛه ड़͢Δ͜ͱ͕΄ͱΜͲͰ͕͢ɺඇಉظॲཧͱ௚ྻॲཧͷ૬ੑ͸࠷ѱͰ͢ɻ͔͠͠ݱࡏͰ ͸௚ྻॲཧͷهड़Λվળ͢ΔݴޠϨϕϧͷ࢓༷͕උΘ͍ͬͯ·͢ɻ ! 39 / 53

Slide 40

Slide 40 text

ϑϩʔ੍ޚֶशͷྲྀΕ ֶशॱ ઃܭύλʔϯ ޓ׵ੑ ϑϩʔ੍ޚ ετϦʔϜॲཧ هड़ྔ  $BMMCBDL ✔  &WFOU&NJUUFS ✔  1SPNJTF ✔ ✔  BTZODBXBJU ✔ ✔ ✔ جૅͷσβΠϯύλʔϯʹΑΔϑϩʔ੍ޚΛɺΑΓ্Ґͷϑϩʔ੍ޚʹม׵͢Δ͜ͱͰֶशΛਐ Ί͍͖ͯ·͢ɻࠓճ͸தؒ૚Ͱ͋ΔPromiseΛεΩοϓͯ͠ɺ࠷ऴతͳண஍఺Ͱ͋Δasync/ awaitΛղઆ͠·͢ɻ ! 40 / 53 ֤ઃܭύλʔϯͷ্Ґޓ׵Ͱ͋Δasync/awaitΛղઆ͠·͢ɻ

Slide 41

Slide 41 text

ίʔϧόοΫʹΑΔϑϩʔ੍ޚ ίʔϧόοΫΛ࢖ͬͨϑϩʔ੍ޚ͸༰қʹཧղͰ͖Δγϯϓϧͳ࢓૊ΈͰ͸͋Γ·͢ ͕ɺॲཧͷ಺༰࣍ୈͰ͸ਂ͍ωετͱ࠶ىॲཧʹΑΓɺෳࡶ͕͞૿͠ՄಡੑͷѱԽͱه ड़ྔͷංେԽʹܨ͕Γ·͢ɻ // 再帰処理による非同期処理のループ const fs = require('fs') const write = (i) => { if (i === 100) return fs.appendFile('./data.txt', `${i},`, (err) => { write(++i) }) } write(0) ϑΝΠϧ΁ͷॻ͖ࠐΈ͕׬ྃͨ͋͠ͱ࣍ͷॲཧΛ։࢝͢Δ ͱ͍͏௚ྻॲཧͰ͕͢ɺඇಉظॲཧͷͨΊϑΝΠϧॻ͖ࠐ Έ׬ྃͷίʔϧόοΫΛى఺ʹ࠶ؼॲཧ͍ͯ͠·͢ɻ ! 41 / 53

Slide 42

Slide 42 text

ϑϩʔ੍ޚ›ඇಉظؔ਺

Slide 43

Slide 43 text

BTZODBXBJU const fs = require('fs').promises const main = async () => { for (let i = 0; i < 100; i++) { await fs.appendFile('./data.txt', `${i},`) } } main() const fs = require('fs') const write = (i) => { if (i === 100) return fs.appendFile('./data.txt', `${i},`, (err) => { write(++i) }) } write(0) ίʔϧόοΫͷ࠶ؼॲཧΛasync/awaitʹม׵͠·͢ɻasync/awaitʹΑͬͯίʔ υελΠϧ͸Ұൠతͳಉظॲཧʹ͍ۙߏ଄ʹมԽ͠·͢ɻͰ͕͢ඇಉظॲཧͷϝϦοτ͸ ଛͳΘΕ͓ͯΒͣɺॲཧͷ࣮ߦத΋ଞͷॲཧʹϦιʔεΛׂΓ౰ͯΔ͜ͱ͕Ͱ͖·͢ɻ $BMMCBDL BTZODBXBJU const data = await func()ͷΑ͏ʹඇ ಉظॲཧͷฦΓ஋Λड͚औΔ͜ͱ΋Ͱ͖·͢ɻ ! 43 / 53

Slide 44

Slide 44 text

BTZODBXBJU const fs = require('fs').promises const main = async () => { for (let i = 0; i < 100; i++) { await fs.appendFile('./data.txt', `${i},`) } } main() asyncfunctionએݴʹΑͬͯඇಉظؔ਺ʢ"TZOD'VOUJPOʣΛఆٛ͠·͢ʢ⁞ʣɻඇಉ ظؔ਺ͷ಺෦Ͱ͸awaitʹΑͬͯඇಉظॲཧͷ׬ྃΛ଴ػͰ͖·͢ʢ ʣɻ Promise͸4USVDUVSFEͳίʔϧόοΫͰ͢ɻ /PEFKTͷҰ෦ͷඪ४Ϟδϡʔϧ͸.promises Λ෇༩͢Δ͜ͱͰίʔϧόοΫͱಉ໊͡લͷ 1SPNJTF"1*Λฦ͠·͢ʢ⁠ʣɻ ௨ৗͷίʔϧόοΫ"1*Ͱ͸awaitʹΑͬͯॲཧͷ׬ྃΛ଴ػͤ͞Δ͜ͱ͕Ͱ͖·ͤΜɻ awaitʹ౉͢ඇಉظॲཧ͸ಛఆͷϧʔϧʹैͬͨΦϒδΣΫτͰ͋Δඞཁ͕͋Γ·͢ɻ ⁠ ⁞   ! 44 / 53

Slide 45

Slide 45 text

 ԼهίʔϧόοΫύλʔϯͷ࠶ؼॲཧΛasync/awaitΛ࢖ͬͨॲཧʹม׵͍ͯͩ͘͠͞ɻ ԋश const fs = require('fs') const write = (i) => { if (i === 100) return fs.appendFile('./data.txt', `${i},`, (err) => { write(++i) }) } write(0) ͜ͷࢿྉͷϦϯΫ https://speakerdeck.com/ajido/minimum-handson-nodejs ԋशϦϯΫ https://chouseisan.com/s?h=4a0d7eb9c38a440e9ad585abfbb16e79 ‣ bit.ly/2XWSQQK εΫϦϓτϦϯΫ https://github.com/ajido/nodejs-handson ! 45 / 53

Slide 46

Slide 46 text

Πϕϯτۦಈύλʔϯͷϑϩʔ੍ޚ try { for await (const keys of stream) { for (const key of keys) { console.log(key) } } } catch (err) { console.error(err) } Πϕϯτۦಈܕͷॲཧ΋forawaitߏจʹΑΓ "TZOD'VODUJPOͷϑϩʔ੍ޚԼʹ૊ΈࠐΉ͜ ͱ͕Ͱ͖·͢ɻ const Redis = require('ioredis') const redis = new Redis() const stream = redis.scanStream({ match: '*', count: 100 }) // 約100件のキーを読み込むごとに呼び出される stream.on('data', (keys) => { for (const key of keys) { console.log(key) } }) // 全てのキーを走査した後に呼び出される stream.on('end', () => { console.log(‘end') }) // 何かしらエラーが発生した時に呼び出される stream.on('error', (err) => { console.error(err) }) ίʔϧόοΫ͕PromiseʹΑͬͯߏ଄Խ͞Ε͍ͯͨΑ͏ ʹɺΠϕϯτۦಈܕͷॲཧ΋stream.ReadableʹΑͬͯ ߏ଄Խ͞Ε͍ͯΔඞཁ͕͋Γ·͢ɻ ! 46 / 53

Slide 47

Slide 47 text

 ԼهΠϕϯτۦಈύλʔϯͷίʔυΛforawaitΛ࢖ͬͨॲཧʹม׵͍ͯͩ͘͠͞ɻ ԋश const fs = require('fs') const rs = fs.createReadStream(__filename) rs.on('data', (data) => { console.log(data.toString()) }) rs.on('error', (err) => { console.error(err) }) ͜ͷࢿྉͷϦϯΫ https://speakerdeck.com/ajido/minimum-handson-nodejs ԋशϦϯΫ https://chouseisan.com/s?h=4a0d7eb9c38a440e9ad585abfbb16e79 ‣ bit.ly/2XWSQQK εΫϦϓτϦϯΫ https://github.com/ajido/nodejs-handson ! 47 / 53

Slide 48

Slide 48 text

BTZODBXBJUͷΤϥʔϋϯυϦϯά "TZOD'VODUJPOͷ಺ଆͰ͸try/catchΛʢ⁞ʣɺ"TZOD'VODUJPOͷ֎ଆͰ ͸.catchΛ࢖ͬͯΤϥʔΛॲཧ͍ͯͩ͘͠͞ʢ ʣɻ const fs = require('fs').promises const main = async () => { for (let i = 0; i < 100; i++) { try { await fs.appendFile('./data.txt', `${i},`) } catch (err) { console.error(err) } } } main().catch((err) => { console.error(err) }) ⁞   "TZOD'VODUJPOͷ಺෦ͰΤϥʔ͕ൃੜ͢ ΔͱɺҎ߱ͷॲཧ͸࣮ߦ͞Εͣ.catchʹ δϟϯϓ͠·͢ɻ ͨͩ͠"TZOD'VODUJPO಺෦ͷΤϥʔൃੜ ෦෼͕try/catchͰแ·Ε͍ͯΔ৔߹͸ɺ try/catchʹัଊ͞Ε.catchʹ͸ಧ͖· ͤΜɻ͜ͷ৔߹try/catchΛൈ͚ͨޙ΋ "TZOD'VODUJPO಺ͷॲཧ͸ܧଓ͞Ε·͢ɻ ! 48 / 53

Slide 49

Slide 49 text

BTZODBXBJUͷΑ͋͘Δࣦഊྫ ⁞awaitͷॻ͖๨Ε  ෆཁͳPromiseͷར༻ ⁠.catchͷॻ͖๨ΕʢΤϥʔϋϯυϦϯάͷهड़࿙Εʣ const fs = require('fs').promises const main = async () => { for (let i = 0; i < 100; i++) { const stat = fs.appendFile('./data.txt', `${i},`).then(() => { return fs.stat(‘./data.txt’) }) // await fs.appendFile('./data.txt', `${i},`) console.log(stat) // const stat = await fs.stat('./data.txt') } return Promise.resolve(‘done') // return 'done' } main().then((result) => console.log(result)) // .catch((err) => console.error(err)) ⁞     ⁠ ! 49 / 53

Slide 50

Slide 50 text

BTZODBXBJUΛ࢖ͬͨแׅతͳΤϥʔϋϯυϦϯά BTZODBXBJU ݫີʹ͸1SPNJTF ͸try/catchͷ໾ׂΛ݉Ͷἧ͍͑ͯ·͢ɻԼهίʔυͰ͸ "TZOD'VODUJPOͷ಺ଆͰൃੜ͢ΔಉظॲཧͷΤϥʔΛ.catchʹٵऩ͍ͤͯ͞·͢ɻॲཧΛ͢ ΂ͯasync/await؀ڥԼʹ࣮૷͢Δ͜ͱͰɺඇಉظɾಉظॲཧΛ໰ΘͣɺแׅతʹΤϥʔΛॲཧ Ͱ͖·͢ɻ const main = async () => { JSON.parse('invalid_json') } main().catch((err) => { console.error(err) }) // SyntaxError: // Unexpected token b in JSON at position 0 // at JSON.parse () try/catchͷهड़࿙ΕʹΑΔϓϩηεμ΢ϯΛ๷͗·͢ɻ͢΂ͯͷ έʔεʹ͓͍ͯasync/awaitΛར༻͢Δͱ͍͏ϧʔϧ͸Ұݟۃ୺ ʹࢥ͑Δ͔΋͠Ε·ͤΜ͕ɺՄಡੑͷ໘Ͱ΋҆શੑͷ໘Ͱ΋͓͢͢Ί Ͱ͖Δ༏Εͨํ๏Ͱ͢ɻ ! 50 / 53

Slide 51

Slide 51 text

νΣοΫϙΠϯτ ‣ BTZODBXBJUͷѻ͍ํΛཧղ͢Δ ‣ BTZODBXBJUͷΤϥʔϋϯυϦϯάํ๏Λཧղ͢Δ ‣ Մಡੑͱ҆શੑΛߟྀ͠ɺՄೳͳݶΓBTZODBXBJUϑΝʔετͷઃܭΛ৺ֻ͚Δ ઃܭύλʔϯ ΤϥʔϋϯυϦϯά Ϋϥογϡ ඇಉظॲཧ $BMMCBDL if (err) &WFOU&NJUUFS emitter.on(‘error') ✔ BTZODBXBJU try/catch  .catch() ಉظॲཧ try/catch BTZODBXBJU ✔ ! 51 / 53

Slide 52

Slide 52 text

جૅ Ԡ༻ ‣ Πϯετʔϧ 5 ‣ ϑϩʔ੍ޚ 38 ‣ Ϟδϡʔϧ؅ཧ 11 ‣ ඇಉظؔ਺ 42 ‣ ಛ௃ 16 ‣ σόοά ‣ σβΠϯύλʔϯ 21 ‣ Ϟδϡʔϧͷ࡞੒ ‣ ίʔϧόοΫ 23 ‣ ϓϩϑΝΠϦϯά ‣ Πϕϯτۦಈ 31 ‣ ϑϨʔϜϫʔΫͱπʔϧ ‣ ಉظॲཧ 35

Slide 53

Slide 53 text

௕͓࣌ؒർΕ༷Ͱͨ͠ɻ Photo by Paul Hanaoka on Unsplash