Repository with sources and generator of https://larlet.fr/david/ https://larlet.fr/david/
Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

index.html 37KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879
  1. <!doctype html><!-- This is a valid HTML5 document. -->
  2. <!-- Screen readers, SEO, extensions and so on. -->
  3. <html lang=fr>
  4. <!-- Has to be within the first 1024 bytes, hence before the <title>
  5. See: https://www.w3.org/TR/2012/CR-html5-20121217/document-metadata.html#charset -->
  6. <meta charset=utf-8>
  7. <!-- Why no `X-UA-Compatible` meta: https://stackoverflow.com/a/6771584 -->
  8. <!-- The viewport meta is quite crowded and we are responsible for that.
  9. See: https://codepen.io/tigt/post/meta-viewport-for-2015 -->
  10. <meta name=viewport content="width=device-width,minimum-scale=1,initial-scale=1,shrink-to-fit=no">
  11. <!-- Required to make a valid HTML5 document. -->
  12. <title>JavaScript, promesses et générateurs — David Larlet</title>
  13. <!-- Generated from https://realfavicongenerator.net/ such a mess. -->
  14. <link rel="apple-touch-icon" sizes="180x180" href="/static/david/icons/apple-touch-icon.png">
  15. <link rel="icon" type="image/png" sizes="32x32" href="/static/david/icons/favicon-32x32.png">
  16. <link rel="icon" type="image/png" sizes="16x16" href="/static/david/icons/favicon-16x16.png">
  17. <link rel="manifest" href="/manifest.json">
  18. <link rel="mask-icon" href="/static/david/icons/safari-pinned-tab.svg" color="#5bbad5">
  19. <link rel="shortcut icon" href="/static/david/icons/favicon.ico">
  20. <meta name="apple-mobile-web-app-title" content="David Larlet">
  21. <meta name="application-name" content="David Larlet">
  22. <meta name="msapplication-TileColor" content="#da532c">
  23. <meta name="msapplication-config" content="/static/david/icons/browserconfig.xml">
  24. <meta name="theme-color" content="#f0f0ea">
  25. <!-- That good ol' feed, subscribe :p. -->
  26. <link rel=alternate type="application/atom+xml" title=Feed href="/david/log/">
  27. <!-- Canonical URL for SEO purposes -->
  28. <link rel="canonical" href="https://larlet.fr/david/blog/2016/javascript-promesses-generateurs/">
  29. <!-- SEO/Semantic metadata -->
  30. <meta name="description" content="Je suis un débutant en JS donc n’hésitez pas à me faire vos retours !" />
  31. <meta name="twitter:description" property="og:description" itemprop="description" content="Je suis un débutant en JS donc n’hésitez pas à me faire vos retours !" />
  32. <meta name="twitter:title" property="og:title" itemprop="name" content="JavaScript, promesses et générateurs" />
  33. <meta name="twitter:card" content="summary" />
  34. <meta name="twitter:creator" content="@davidbgk" />
  35. <meta name="twitter:url" property="og:url" content="https://larlet.fr/david/blog/2016/javascript-promesses-generateurs/" />
  36. <meta property="og:type" content="article" />
  37. <meta property="og:site_name" content="David Larlet (@davidbgk)" />
  38. <meta name="twitter:image" property="og:image" itemprop="image" content="https://larlet.fr/static/david/blog/2016/javascript-promesses-generateurs.jpg" />
  39. <style>
  40. /* http://meyerweb.com/eric/tools/css/reset/ */
  41. html, body, div, span,
  42. h1, h2, h3, h4, h5, h6, p, blockquote, pre,
  43. a, abbr, address, big, cite, code,
  44. del, dfn, em, img, ins,
  45. small, strike, strong, tt, var,
  46. dl, dt, dd, ol, ul, li,
  47. fieldset, form, label, legend,
  48. table, caption, tbody, tfoot, thead, tr, th, td,
  49. article, aside, canvas, details, embed,
  50. figure, figcaption, footer, header, hgroup,
  51. menu, nav, output, ruby, section, summary,
  52. time, mark, audio, video {
  53. margin: 0;
  54. padding: 0;
  55. border: 0;
  56. font-size: 100%;
  57. font: inherit;
  58. vertical-align: baseline;
  59. }
  60. /* HTML5 display-role reset for older browsers */
  61. article, aside, details, figcaption, figure,
  62. footer, header, hgroup, menu, nav, section { display: block; }
  63. body { line-height: 1; }
  64. blockquote, q { quotes: none; }
  65. blockquote:before, blockquote:after,
  66. q:before, q:after {
  67. content: '';
  68. content: none;
  69. }
  70. table {
  71. border-collapse: collapse;
  72. border-spacing: 0;
  73. }
  74. /* http://practicaltypography.com/equity.html */
  75. /* https://calendar.perfplanet.com/2016/no-font-face-bulletproof-syntax/ */
  76. /* https://www.filamentgroup.com/lab/js-web-fonts.html */
  77. @font-face {
  78. font-family: 'EquityTextB';
  79. src: url('/static/david/css/fonts/Equity-Text-B-Regular-webfont.woff2') format('woff2'),
  80. url('/static/david/css/fonts/Equity-Text-B-Regular-webfont.woff') format('woff');
  81. font-weight: 300;
  82. font-style: normal;
  83. font-display: swap;
  84. }
  85. @font-face {
  86. font-family: 'EquityTextB';
  87. src: url('/static/david/css/fonts/Equity-Text-B-Italic-webfont.woff2') format('woff2'),
  88. url('/static/david/css/fonts/Equity-Text-B-Italic-webfont.woff') format('woff');
  89. font-weight: 300;
  90. font-style: italic;
  91. font-display: swap;
  92. }
  93. @font-face {
  94. font-family: 'EquityTextB';
  95. src: url('/static/david/css/fonts/Equity-Text-B-Bold-webfont.woff2') format('woff2'),
  96. url('/static/david/css/fonts/Equity-Text-B-Bold-webfont.woff') format('woff');
  97. font-weight: 700;
  98. font-style: normal;
  99. font-display: swap;
  100. }
  101. @font-face {
  102. font-family: 'ConcourseT3';
  103. src: url('/static/david/css/fonts/concourse_t3_regular-webfont-20190806.woff2') format('woff2'),
  104. url('/static/david/css/fonts/concourse_t3_regular-webfont-20190806.woff') format('woff');
  105. font-weight: 300;
  106. font-style: normal;
  107. font-display: swap;
  108. }
  109. /* http://practice.typekit.com/lesson/caring-about-opentype-features/ */
  110. body {
  111. /* http://www.cssfontstack.com/ Palatino 99% Win 86% Mac */
  112. font-family: "EquityTextB", Palatino, serif;
  113. background-color: #f0f0ea;
  114. color: #07486c;
  115. font-kerning: normal;
  116. -moz-osx-font-smoothing: grayscale;
  117. -webkit-font-smoothing: subpixel-antialiased;
  118. text-rendering: optimizeLegibility;
  119. font-variant-ligatures: common-ligatures contextual;
  120. font-feature-settings: "kern", "liga", "clig", "calt";
  121. }
  122. pre, code, kbd, samp, var, tt {
  123. font-family: 'TriplicateT4c', monospace;
  124. }
  125. em {
  126. font-style: italic;
  127. color: #323a45;
  128. }
  129. strong {
  130. font-weight: bold;
  131. color: black;
  132. }
  133. nav {
  134. background-color: #323a45;
  135. color: #f0f0ea;
  136. display: flex;
  137. justify-content: space-around;
  138. padding: 1rem .5rem;
  139. }
  140. nav:last-child {
  141. border-bottom: 1vh solid #2d7474;
  142. }
  143. nav a {
  144. color: #f0f0ea;
  145. }
  146. nav abbr {
  147. border-bottom: 1px dotted white;
  148. }
  149. h1 {
  150. border-top: 1vh solid #2d7474;
  151. border-bottom: .2vh dotted #2d7474;
  152. background-color: #e3e1e1;
  153. color: #323a45;
  154. text-align: center;
  155. padding: 5rem 0 4rem 0;
  156. width: 100%;
  157. font-family: 'ConcourseT3';
  158. display: flex;
  159. flex-direction: column;
  160. }
  161. h1.single {
  162. padding-bottom: 10rem;
  163. }
  164. h1 span {
  165. position: absolute;
  166. top: 1vh;
  167. left: 20%;
  168. line-height: 0;
  169. }
  170. h1 span a {
  171. line-height: 1.7;
  172. padding: 1rem 1.2rem .6rem 1.2rem;
  173. border-radius: 0 0 6% 6%;
  174. background: #2d7474;
  175. font-size: 1.3rem;
  176. color: white;
  177. text-decoration: none;
  178. }
  179. h2 {
  180. margin: 4rem 0 1rem;
  181. border-top: .2vh solid #2d7474;
  182. padding-top: 1vh;
  183. }
  184. h3 {
  185. text-align: center;
  186. margin: 3rem 0 .75em;
  187. }
  188. hr {
  189. height: .4rem;
  190. width: .4rem;
  191. border-radius: .4rem;
  192. background: #07486c;
  193. margin: 2.5rem auto;
  194. }
  195. time {
  196. display: bloc;
  197. margin-left: 0 !important;
  198. }
  199. ul, ol {
  200. margin: 2rem;
  201. }
  202. ul {
  203. list-style-type: square;
  204. }
  205. a {
  206. text-decoration-skip-ink: auto;
  207. text-decoration-thickness: 0.05em;
  208. text-underline-offset: 0.09em;
  209. }
  210. article {
  211. max-width: 50rem;
  212. display: flex;
  213. flex-direction: column;
  214. margin: 2rem auto;
  215. }
  216. article.single {
  217. border-top: .2vh dotted #2d7474;
  218. margin: -6rem auto 1rem auto;
  219. background: #f0f0ea;
  220. padding: 2rem;
  221. }
  222. article p:last-child {
  223. margin-bottom: 1rem;
  224. }
  225. p {
  226. padding: 0 .5rem;
  227. margin-left: 3rem;
  228. }
  229. p + p,
  230. figure + p {
  231. margin-top: 2rem;
  232. }
  233. blockquote {
  234. background-color: #e3e1e1;
  235. border-left: .5vw solid #2d7474;
  236. display: flex;
  237. flex-direction: column;
  238. align-items: center;
  239. padding: 1rem;
  240. margin: 1.5rem;
  241. }
  242. blockquote cite {
  243. font-style: italic;
  244. }
  245. blockquote p {
  246. margin-left: 0;
  247. }
  248. figure {
  249. border-top: .2vh solid #2d7474;
  250. background-color: #e3e1e1;
  251. text-align: center;
  252. padding: 1.5rem 0;
  253. margin: 1rem 0 0;
  254. font-size: 1.5rem;
  255. width: 100%;
  256. }
  257. figure img {
  258. max-width: 250px;
  259. max-height: 250px;
  260. border: .5vw solid #323a45;
  261. padding: 1px;
  262. }
  263. figcaption {
  264. padding: 1rem;
  265. line-height: 1.4;
  266. }
  267. aside {
  268. display: flex;
  269. flex-direction: column;
  270. background-color: #e3e1e1;
  271. padding: 1rem 0;
  272. border-bottom: .2vh solid #07486c;
  273. }
  274. aside p {
  275. max-width: 50rem;
  276. margin: 0 auto;
  277. }
  278. /* https://fvsch.com/code/css-locks/ */
  279. p, li, pre, code, kbd, samp, var, tt, time, details, figcaption {
  280. font-size: 1rem;
  281. line-height: calc( 1.5em + 0.2 * 1rem );
  282. }
  283. h1 {
  284. font-size: 1.9rem;
  285. line-height: calc( 1.2em + 0.2 * 1rem );
  286. }
  287. h2 {
  288. font-size: 1.6rem;
  289. line-height: calc( 1.3em + 0.2 * 1rem );
  290. }
  291. h3 {
  292. font-size: 1.35rem;
  293. line-height: calc( 1.4em + 0.2 * 1rem );
  294. }
  295. @media (min-width: 20em) {
  296. /* The (100vw - 20rem) / (50 - 20) part
  297. resolves to 0-1rem, depending on the
  298. viewport width (between 20em and 50em). */
  299. p, li, pre, code, kbd, samp, var, tt, time, details, figcaption {
  300. font-size: calc( 1rem + .6 * (100vw - 20rem) / (50 - 20) );
  301. line-height: calc( 1.5em + 0.2 * (100vw - 50rem) / (20 - 50) );
  302. margin-left: 0;
  303. }
  304. h1 {
  305. font-size: calc( 1.9rem + 1.5 * (100vw - 20rem) / (50 - 20) );
  306. line-height: calc( 1.2em + 0.2 * (100vw - 50rem) / (20 - 50) );
  307. }
  308. h2 {
  309. font-size: calc( 1.5rem + 1.5 * (100vw - 20rem) / (50 - 20) );
  310. line-height: calc( 1.3em + 0.2 * (100vw - 50rem) / (20 - 50) );
  311. }
  312. h3 {
  313. font-size: calc( 1.35rem + 1.5 * (100vw - 20rem) / (50 - 20) );
  314. line-height: calc( 1.4em + 0.2 * (100vw - 50rem) / (20 - 50) );
  315. }
  316. }
  317. @media (min-width: 50em) {
  318. /* The right part of the addition *must* be a
  319. rem value. In this example we *could* change
  320. the whole declaration to font-size:2.5rem,
  321. but if our baseline value was not expressed
  322. in rem we would have to use calc. */
  323. p, li, pre, code, kbd, samp, var, tt, time, details, figcaption {
  324. font-size: calc( 1rem + .6 * 1rem );
  325. line-height: 1.5em;
  326. }
  327. p, li, pre, details {
  328. margin-left: 3rem;
  329. }
  330. h1 {
  331. font-size: calc( 1.9rem + 1.5 * 1rem );
  332. line-height: 1.2em;
  333. }
  334. h2 {
  335. font-size: calc( 1.5rem + 1.5 * 1rem );
  336. line-height: 1.3em;
  337. }
  338. h3 {
  339. font-size: calc( 1.35rem + 1.5 * 1rem );
  340. line-height: 1.4em;
  341. }
  342. figure img {
  343. max-width: 500px;
  344. max-height: 500px;
  345. }
  346. }
  347. figure.unsquared {
  348. margin-bottom: 1.5rem;
  349. }
  350. figure.unsquared img {
  351. height: inherit;
  352. }
  353. @media print {
  354. body { font-size: 100%; }
  355. a:after { content: " (" attr(href) ")"; }
  356. a, a:link, a:visited, a:after {
  357. text-decoration: underline;
  358. text-shadow: none !important;
  359. background-image: none !important;
  360. background: white;
  361. color: black;
  362. }
  363. abbr[title] { border-bottom: 0; }
  364. abbr[title]:after { content: " (" attr(title) ")"; }
  365. img { page-break-inside: avoid; }
  366. @page { margin: 2cm .5cm; }
  367. h1, h2, h3 { page-break-after: avoid; }
  368. p3 { orphans: 3; widows: 3; }
  369. img {
  370. max-width: 250px !important;
  371. max-height: 250px !important;
  372. }
  373. nav, aside { display: none; }
  374. }
  375. ul.with_columns {
  376. column-count: 1;
  377. }
  378. @media (min-width: 20em) {
  379. ul.with_columns {
  380. column-count: 2;
  381. }
  382. }
  383. @media (min-width: 50em) {
  384. ul.with_columns {
  385. column-count: 3;
  386. }
  387. }
  388. ul.with_two_columns {
  389. column-count: 1;
  390. }
  391. @media (min-width: 20em) {
  392. ul.with_two_columns {
  393. column-count: 1;
  394. }
  395. }
  396. @media (min-width: 50em) {
  397. ul.with_two_columns {
  398. column-count: 2;
  399. }
  400. }
  401. .gallery {
  402. display: flex;
  403. flex-wrap: wrap;
  404. justify-content: space-around;
  405. }
  406. .gallery figure img {
  407. margin-left: 1rem;
  408. margin-right: 1rem;
  409. }
  410. .gallery figure figcaption {
  411. font-family: 'ConcourseT3'
  412. }
  413. footer {
  414. font-family: 'ConcourseT3';
  415. display: flex;
  416. flex-direction: column;
  417. border-top: 3px solid white;
  418. padding: 4rem 0;
  419. background-color: #07486c;
  420. color: white;
  421. }
  422. footer > * {
  423. max-width: 50rem;
  424. margin: 0 auto;
  425. }
  426. footer a {
  427. color: #f1c40f;
  428. }
  429. footer .avatar {
  430. width: 200px;
  431. height: 200px;
  432. border-radius: 50%;
  433. float: left;
  434. -webkit-shape-outside: circle();
  435. shape-outside: circle();
  436. margin-right: 2rem;
  437. padding: 2px 5px 5px 2px;
  438. background: white;
  439. border-left: 1px solid #f1c40f;
  440. border-top: 1px solid #f1c40f;
  441. border-right: 5px solid #f1c40f;
  442. border-bottom: 5px solid #f1c40f;
  443. }
  444. </style>
  445. <h1 class="single">
  446. <span><a id="jumper" href="#jumpto" title="Un peu perdu ?">?</a></span>
  447. JavaScript, promesses et générateurs
  448. <time>Publié le 17 juin 2016</time>
  449. </h1>
  450. <article class="single">
  451. <blockquote>
  452. <p>Conceptually that’s a much cleaner and easier to understand process than what happens when promises are chained multiple times. If you’ve ever tried to understand just how a promise chain is wired up, or explain it to someone else, you’ll know it’s not so simple. I won’t belabor those details here, but I’ve <a href="https://blog.getify.com/promises-part-5/#the-chains-that-bind-us">previously explained it in more detail</a> if you care to dig into it.</p>
  453. <p>The sync-async pattern above wipes away any of that unnecessary complexity. It also conveniently hides the <code>then(..)</code> calling altogether, because truthfully, that part is a wart. The <code>then(..)</code> on a promise is an unfortunate but necessary component of the Promise API design that was settled on for JS.</p>
  454. <p><cite><em><a href="https://blog.getify.com/promises-wrong-ways/">Promises: All The Wrong Ways</a></em> (<a href="/david/cache/2a774113a3de3e53ca5bf9d45d0b31d1/">cache</a>)</cite></p>
  455. </blockquote>
  456. <p>À la lecture de cet article et utilisant <code>fetch</code> avec <a href="https://github.com/github/fetch">son polyfill</a> sur les <a href="https://github.com/etalab/">projets Etalab</a>, j’ai décidé d’expérimenter ce qui pouvait être fait à base de générateurs pour éviter d’enchaîner les promesses. Ce billet technique sur JavaScript (<a href="/david/blog/2016/javascript-reduit/">encore !</a>) n’est pas une introduction à <a href="https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Promise">l’objet Promise</a> mais un usage possible, si vous n’avez jamais manipulé ça risque d’être un peu difficile à comprendre. Si vous êtes familier avec les générateurs Python ça peut aider :-).</p>
  457. <p>Prenons un exemple classique où vous voulez récupérer des données depuis une API HTTP :</p>
  458. <div class="codehilite"><pre><span class="kr">const</span> <span class="nx">url</span> <span class="o">=</span> <span class="s1">&#39;http://httpbin.org/get&#39;</span>
  459. <span class="nx">fetch</span><span class="p">(</span><span class="nx">url</span><span class="p">)</span>
  460. </pre></div>
  461. <p>Jusqu’ici tout va bien mais dès la prochaine ligne ça se complique car il va falloir gérer <a href="https://github.com/github/fetch#handling-http-error-statuses">les codes d’erreurs à la main</a> :</p>
  462. <div class="codehilite"><pre><span class="kd">function</span> <span class="nx">checkStatus</span> <span class="p">(</span><span class="nx">response</span><span class="p">)</span> <span class="p">{</span>
  463. <span class="k">if</span> <span class="p">(</span><span class="nx">response</span><span class="p">.</span><span class="nx">status</span> <span class="o">&gt;=</span> <span class="mi">200</span> <span class="o">&amp;&amp;</span> <span class="nx">response</span><span class="p">.</span><span class="nx">status</span> <span class="o">&lt;</span> <span class="mi">300</span><span class="p">)</span> <span class="p">{</span>
  464. <span class="k">return</span> <span class="nx">response</span>
  465. <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
  466. <span class="kr">const</span> <span class="nx">error</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Error</span><span class="p">(</span><span class="nx">response</span><span class="p">.</span><span class="nx">statusText</span><span class="p">)</span>
  467. <span class="nx">error</span><span class="p">.</span><span class="nx">response</span> <span class="o">=</span> <span class="nx">response</span>
  468. <span class="k">throw</span> <span class="nx">error</span>
  469. <span class="p">}</span>
  470. <span class="p">}</span>
  471. </pre></div>
  472. <p>Ici on ne retourne la réponse que si elle est en 2XX et sinon on lève notre propre erreur. On peut maintenant enchaîner l’exécution de cette fonction au retour de notre <code>fetch</code> :</p>
  473. <div class="codehilite"><pre><span class="p">.</span><span class="nx">then</span><span class="p">(</span><span class="nx">checkStatus</span><span class="p">)</span>
  474. </pre></div>
  475. <p>Puis récupérer la réponse sous forme de JSON :</p>
  476. <div class="codehilite"><pre><span class="p">.</span><span class="nx">then</span><span class="p">((</span><span class="nx">response</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">response</span><span class="p">.</span><span class="nx">json</span><span class="p">())</span>
  477. </pre></div>
  478. <p>Et enfin faire quelque chose d’utile avec :</p>
  479. <div class="codehilite"><pre><span class="p">.</span><span class="nx">then</span><span class="p">((</span><span class="nx">jsonResponse</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
  480. <span class="c1">// Do something with the jsonResponse.</span>
  481. <span class="p">})</span>
  482. </pre></div>
  483. <p>Si vous en restez là en cas d’erreur vous n’avez pas de moyen de savoir ce qu’il se passe, il ne faut jamais oublier d’ajouter un <code>catch</code> à vos chaînes de promesses :</p>
  484. <div class="codehilite"><pre><span class="p">.</span><span class="k">catch</span><span class="p">(</span><span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">.</span><span class="nx">bind</span><span class="p">(</span><span class="nx">console</span><span class="p">))</span>
  485. </pre></div>
  486. <p>Ici on fait le minimum en affichant un message dans la console. Investiguer des bugs liés à de l’asynchrone peut rapidement s’avérer être prise de tête si vous ne prenez pas ces précautions. Au même titre, ne mélangez <em>jamais</em> différentes implémentations des promises sous peine de devoir re-standardiser ensuite toutes vos promesses via <code>Promise.resolve(promesse)</code>…</p>
  487. <p>Donc au final notre promesse chaînée complète ressemble à :</p>
  488. <div class="codehilite"><pre><span class="nx">fetch</span><span class="p">(</span><span class="nx">url</span><span class="p">)</span>
  489. <span class="p">.</span><span class="nx">then</span><span class="p">(</span><span class="nx">checkStatus</span><span class="p">)</span>
  490. <span class="p">.</span><span class="nx">then</span><span class="p">((</span><span class="nx">response</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">response</span><span class="p">.</span><span class="nx">json</span><span class="p">())</span>
  491. <span class="p">.</span><span class="nx">then</span><span class="p">((</span><span class="nx">jsonResponse</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
  492. <span class="c1">// Do something with the jsonResponse.</span>
  493. <span class="p">})</span>
  494. <span class="p">.</span><span class="k">catch</span><span class="p">(</span><span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">.</span><span class="nx">bind</span><span class="p">(</span><span class="nx">console</span><span class="p">))</span>
  495. </pre></div>
  496. <p>Ça reste lisible mais c’est <a href="https://www.youtube.com/watch?v=oqwzuiSy9y0">loin d’être idéal</a>, je vous laisse aller lire <a href="https://blog.getify.com/promises-wrong-ways/">l’article en intro</a> (<a href="/david/cache/2a774113a3de3e53ca5bf9d45d0b31d1/">cache</a>) pour les différentes raisons.</p>
  497. <p>En utilisant un générateur, je suis arrivé à ce résultat :</p>
  498. <div class="codehilite"><pre><span class="nx">run</span><span class="p">(</span><span class="nx">fetchJSON</span><span class="p">,</span> <span class="nx">url</span><span class="p">)</span>
  499. <span class="p">.</span><span class="nx">then</span><span class="p">((</span><span class="nx">jsonResponse</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
  500. <span class="c1">// Do something with the jsonResponse.</span>
  501. <span class="p">},</span> <span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">.</span><span class="nx">bind</span><span class="p">(</span><span class="nx">console</span><span class="p">))</span>
  502. </pre></div>
  503. <p>Qui est finalement la seule logique métier qui m’intéresse. Un seul <code>.then()</code> permettant de gérer le <code>fulfilled</code>/<code>rejected</code> directement en paramètre sans passer par le <code>.catch()</code>. Ensuite notre <code>fetchJSON</code> est un générateur <code>*</code> :</p>
  504. <div class="codehilite"><pre><span class="kd">function</span> <span class="o">*</span> <span class="nx">fetchJSON</span> <span class="p">(</span><span class="nx">url</span><span class="p">)</span> <span class="p">{</span>
  505. <span class="kr">const</span> <span class="nx">rawResponse</span> <span class="o">=</span> <span class="nx">yield</span> <span class="nx">fetch</span><span class="p">(</span><span class="nx">url</span><span class="p">)</span>
  506. <span class="kr">const</span> <span class="nx">validResponse</span> <span class="o">=</span> <span class="nx">yield</span> <span class="nx">checkStatus</span><span class="p">(</span><span class="nx">rawResponse</span><span class="p">)</span>
  507. <span class="k">return</span> <span class="nx">validResponse</span><span class="p">.</span><span class="nx">json</span><span class="p">()</span>
  508. <span class="p">}</span>
  509. </pre></div>
  510. <p>Proche de la syntaxe Python, on retrouve le mot-clé <code>yield</code> qui sert à récupérer les différents éléments de l’itérateur généré. Ici il y a exactement le même traitement que précédemment de manière un peu plus explicite à mon goût. Il reste à définir notre <em>runner</em> :</p>
  511. <div class="codehilite"><pre><span class="kd">function</span> <span class="nx">run</span> <span class="p">(</span><span class="nx">generator</span><span class="p">,</span> <span class="p">...</span><span class="nx">args</span><span class="p">)</span> <span class="p">{</span>
  512. <span class="kr">const</span> <span class="nx">iterator</span> <span class="o">=</span> <span class="nx">generator</span><span class="p">(...</span><span class="nx">args</span><span class="p">)</span>
  513. <span class="k">return</span> <span class="nx">Promise</span><span class="p">.</span><span class="nx">resolve</span><span class="p">()</span>
  514. <span class="p">.</span><span class="nx">then</span><span class="p">(</span><span class="kd">function</span> <span class="nx">handleNext</span> <span class="p">(</span><span class="nx">value</span><span class="p">)</span> <span class="p">{</span>
  515. <span class="kr">const</span> <span class="nx">next</span> <span class="o">=</span> <span class="nx">iterator</span><span class="p">.</span><span class="nx">next</span><span class="p">(</span><span class="nx">value</span><span class="p">)</span>
  516. <span class="k">return</span> <span class="p">(</span><span class="kd">function</span> <span class="nx">handleResult</span> <span class="p">(</span><span class="nx">next</span><span class="p">)</span> <span class="p">{</span>
  517. <span class="k">if</span> <span class="p">(</span><span class="nx">next</span><span class="p">.</span><span class="nx">done</span><span class="p">)</span> <span class="p">{</span>
  518. <span class="k">return</span> <span class="nx">next</span><span class="p">.</span><span class="nx">value</span>
  519. <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
  520. <span class="k">return</span> <span class="nx">Promise</span><span class="p">.</span><span class="nx">resolve</span><span class="p">(</span><span class="nx">next</span><span class="p">.</span><span class="nx">value</span><span class="p">)</span>
  521. <span class="p">.</span><span class="nx">then</span><span class="p">(</span>
  522. <span class="nx">handleNext</span><span class="p">,</span>
  523. <span class="p">(</span><span class="nx">error</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
  524. <span class="k">return</span> <span class="nx">Promise</span><span class="p">.</span><span class="nx">resolve</span><span class="p">(</span><span class="nx">iterator</span><span class="p">.</span><span class="k">throw</span><span class="p">(</span><span class="nx">error</span><span class="p">))</span>
  525. <span class="p">.</span><span class="nx">then</span><span class="p">(</span><span class="nx">handleResult</span><span class="p">)</span>
  526. <span class="p">}</span>
  527. <span class="p">)</span>
  528. <span class="p">}</span>
  529. <span class="p">})(</span><span class="nx">next</span><span class="p">)</span>
  530. <span class="p">})</span>
  531. <span class="p">}</span>
  532. </pre></div>
  533. <p>Ici c’est la gestion des erreurs qui rend le script un peu velu mais en gros ça suit l’itérateur créé à partir du générateur de manière récursive en retournant des promesses (de promesses (de promesses (etc))). C’est là où se cache maintenant la complexité de vouloir faire de l’asynchrone séquentiel. L’avantage c’est que c’est localisé et réutilisable ensuite pour toutes vos séquences. En attendant <a href="https://blog.getify.com/not-awaiting-1/">async/await</a> (<a href="/david/cache/bfebf03c42d9609fef758bfc9a305f81/">cache</a>). Ou <a href="https://blog.getify.com/not-awaiting-2">pas</a> (<a href="/david/cache/dc98d4cae65338a1be618bab8fa43177/">cache</a>).</p>
  534. <p><strong>Le gros point noir dans tout ça, c’est lorsque vous tentez la conversion en ES5 avec Babel.</strong> Vous devez utiliser <a href="https://babeljs.io/docs/usage/polyfill/">babel-polyfill</a> qui rajoute immédiatement 300Kb à votre joli code… ce qui est envisageable côté serveur ou pour une application complète devient rédhibitoire pour une simple bibliothèque. Même si on ne va au final pas l’utiliser pour cette raison là, ça aura été une incursion intéressante dans l’univers asynchrone de JavaScript en attendant que les fonctionnalités d’ES6/ES7 soient davantage implémentées nativement.</p>
  535. <p><em>Je suis un débutant en JS donc n’hésitez pas à me faire vos retours si j’ai raconté des bêtises !</em></p>
  536. </article>
  537. <figure class="image" property="schema:image">
  538. <img src="/static/david/blog/2016/javascript-promesses-generateurs.jpg" alt="" />
  539. </figure>
  540. <nav id="jumpto">
  541. <p>
  542. <a rel=prev href="/david/blog/2016/instantane-scopyleft/">← Instantané Scopyleft</a> | <a href="/david/blog/" title="Retour à la liste des expériences">↑</a> | <a rel=next href="/david/blog/2016/code-reviews-croisees/">Code reviews croisées →</a>
  543. </p>
  544. </nav>
  545. <footer>
  546. <div>
  547. <img src="/static/david/david-larlet-avatar.jpg" loading="lazy" class="avatar" width="200" height="200">
  548. <p>
  549. Bonjour/Hi!
  550. Je suis <a href="/david/" title="Profil public">David&nbsp;Larlet</a>, je vis actuellement à Montréal et j’alimente cet espace depuis 15 ans. <br>
  551. Si tu as apprécié cette lecture, n’hésite pas à poursuivre ton exploration. Par exemple via les <a href="/david/blog/" title="Expériences bienveillantes">réflexions bimestrielles</a>, la <a href="/david/stream/2019/" title="Pensées (dés)articulées">veille hebdomadaire</a> ou en t’abonnant au <a href="/david/log/" title="S’abonner aux publications via RSS">flux RSS</a> (<a href="/david/blog/2019/flux-rss/" title="Tiens c’est quoi un flux RSS ?">so 2005</a>).
  552. </p>
  553. <p>
  554. Je m’intéresse à la place que je peux avoir dans ce monde. En tant qu’humain, en tant que membre d’une famille et en tant qu’associé d’une coopérative. De temps en temps, je fais aussi des <a href="https://github.com/davidbgk" title="Principalement sur Github mais aussi ailleurs">trucs techniques</a>. Et encore plus rarement, <a href="/david/talks/" title="En ce moment je laisse plutôt la place aux autres">j’en parle</a>.
  555. </p>
  556. <p>
  557. Les dernières publications hebdomadaires sont :
  558. </p>
  559. <ul class="with_columns">
  560. <li>
  561. <a href="/david/stream/2019/12/17/">Origines</a>
  562. </li>
  563. <li>
  564. <a href="/david/stream/2019/12/10/">Publier</a>
  565. </li>
  566. <li>
  567. <a href="/david/stream/2019/12/03/">En forêt</a>
  568. </li>
  569. <li>
  570. <a href="/david/stream/2019/11/26/">Ecocentric</a>
  571. </li>
  572. <li>
  573. <a href="/david/stream/2019/11/19/">Se livrer</a>
  574. </li>
  575. <li>
  576. <a href="/david/stream/2019/11/12/">Dépendances</a>
  577. </li>
  578. <li>
  579. <a href="/david/stream/2019/11/05/">Positif</a>
  580. </li>
  581. <li>
  582. <a href="/david/stream/2019/10/29/">Dettes</a>
  583. </li>
  584. <li>
  585. <a href="/david/stream/2019/10/22/">Privilèges</a>
  586. </li>
  587. <li>
  588. <a href="/david/stream/2019/10/15/">Discrétion</a>
  589. </li>
  590. <li>
  591. <a href="/david/stream/2019/10/08/">Désespérance</a>
  592. </li>
  593. <li>
  594. <a href="/david/stream/2019/10/01/">Présent</a>
  595. </li>
  596. <li>
  597. <a href="/david/stream/2019/09/24/">Manifester</a>
  598. </li>
  599. <li>
  600. <a href="/david/stream/2019/09/17/">Arpenter</a>
  601. </li>
  602. <li>
  603. <a href="/david/stream/2019/09/10/">Nostalgie</a>
  604. </li>
  605. <li>
  606. <a href="/david/stream/2019/09/03/">Déconstruire</a>
  607. </li>
  608. <li>
  609. <a href="/david/stream/2019/08/27/">Documenter</a>
  610. </li>
  611. <li>
  612. <a href="/david/stream/2019/08/20/">Frustration</a>
  613. </li>
  614. <li>
  615. <a href="/david/stream/2019/08/13/">Holisme</a>
  616. </li>
  617. <li>
  618. <a href="/david/stream/2019/08/06/">1%</a>
  619. </li>
  620. <li>
  621. <a href="/david/stream/2019/07/30/">Exemplarité</a>
  622. </li>
  623. <li>
  624. <a href="/david/stream/2019/07/23/">Timelines</a>
  625. </li>
  626. <li>
  627. <a href="/david/stream/2019/07/16/">Écoute</a>
  628. </li>
  629. <li>
  630. <a href="/david/stream/2019/07/02/">Anxiété</a>
  631. </li>
  632. <li>
  633. <a href="/david/stream/2019/06/21/">À lier</a>
  634. </li>
  635. <li>
  636. <a href="/david/stream/2019/06/14/">Pauvreté</a>
  637. </li>
  638. <li>
  639. <a href="/david/stream/2019/06/07/">Amateur</a>
  640. </li>
  641. <li>
  642. <a href="/david/stream/2019/05/31/">Pollution</a>
  643. </li>
  644. <li>
  645. <a href="/david/stream/2019/05/24/">Apaisement</a>
  646. </li>
  647. <li>
  648. <a href="/david/stream/2019/05/10/">Folie</a>
  649. </li>
  650. <li>
  651. <a href="/david/stream/2019/05/03/">Sympathie</a>
  652. </li>
  653. <li>
  654. <a href="/david/stream/2019/04/12/">Péremption</a>
  655. </li>
  656. <li>
  657. <a href="/david/stream/2019/04/05/">Définitions</a>
  658. </li>
  659. <li>
  660. <a href="/david/stream/2019/03/29/">Acceptation</a>
  661. </li>
  662. <li>
  663. <a href="/david/stream/2019/03/22/">Dissonance</a>
  664. </li>
  665. <li>
  666. <a href="/david/stream/2019/03/15/">Reconnaissance</a>
  667. </li>
  668. <li>
  669. <a href="/david/stream/2019/03/08/">Lecture</a>
  670. </li>
  671. <li>
  672. <a href="/david/stream/2019/03/01/">Journaux</a>
  673. </li>
  674. <li>
  675. <a href="/david/stream/2019/02/22/">Écriture</a>
  676. </li>
  677. <li>
  678. <a href="/david/stream/2019/02/15/">Kyriarchie</a>
  679. </li>
  680. <li>
  681. <a href="/david/stream/2019/02/08/">Mots-serrures</a>
  682. </li>
  683. <li>
  684. <a href="/david/stream/2019/02/01/">Sans voie</a>
  685. </li>
  686. <li>
  687. <a href="/david/stream/2019/01/25/">Auto-diagnostic</a>
  688. </li>
  689. <li>
  690. <a href="/david/stream/2019/01/18/">Agilité</a>
  691. </li>
  692. <li>
  693. <a href="/david/stream/2019/01/11/">Métaphores</a>
  694. </li>
  695. <li>
  696. <a href="/david/stream/2019/01/04/">Balbutiements</a>
  697. </li>
  698. </ul>
  699. <p>
  700. Voici quelques articles choisis :
  701. <a href="/david/blog/2019/faire-equipe/" title="Accéder à l’article complet">Faire équipe</a>,
  702. <a href="/david/blog/2018/bivouac-automnal/" title="Accéder à l’article complet">Bivouac automnal</a>,
  703. <a href="/david/blog/2018/commodite-effondrement/" title="Accéder à l’article complet">Commodité et effondrement</a>,
  704. <a href="/david/blog/2017/donnees-communs/" title="Accéder à l’article complet">Des données aux communs</a>,
  705. <a href="/david/blog/2016/accompagner-enfant/" title="Accéder à l’article complet">Accompagner un enfant</a>,
  706. <a href="/david/blog/2016/senior-developer/" title="Accéder à l’article complet">Senior developer</a>,
  707. <a href="/david/blog/2016/illusion-sociale/" title="Accéder à l’article complet">L’illusion sociale</a>,
  708. <a href="/david/blog/2016/instantane-scopyleft/" title="Accéder à l’article complet">Instantané Scopyleft</a>,
  709. <a href="/david/blog/2016/enseigner-web/" title="Accéder à l’article complet">Enseigner le Web</a>,
  710. <a href="/david/blog/2016/simplicite-defaut/" title="Accéder à l’article complet">Simplicité par défaut</a>,
  711. <a href="/david/blog/2016/minimalisme-esthetique/" title="Accéder à l’article complet">Minimalisme et esthétique</a>,
  712. <a href="/david/blog/2014/un-web-omni-present/" title="Accéder à l’article complet">Un web omni-présent</a>,
  713. <a href="/david/blog/2014/manifeste-developpeur/" title="Accéder à l’article complet">Manifeste de développeur</a>,
  714. <a href="/david/blog/2013/confort-convivialite/" title="Accéder à l’article complet">Confort et convivialité</a>,
  715. <a href="/david/blog/2013/testament-numerique/" title="Accéder à l’article complet">Testament numérique</a>,
  716. et <a href="/david/blog/" title="Accéder aux archives">bien d’autres…</a>
  717. </p>
  718. <p>
  719. On peut <a href="mailto:david%40larlet.fr" title="Envoyer un courriel">échanger par courriel</a>. Si éventuellement tu souhaites que l’on travaille ensemble, tu devrais commencer par consulter le <a href="http://larlet.com">profil dédié à mon activité professionnelle</a> et/ou contacter directement <a href="http://scopyleft.fr/">scopyleft</a>, la <abbr title="Société coopérative et participative">SCOP</abbr> dont je fais partie depuis six ans. Je recommande au préalable de lire <a href="/david/blog/2018/cout-site/" title="Attention ce qui va suivre peut vous choquer">combien coûte un site</a> et pourquoi je suis plutôt favorable à une <a href="/david/pro/devis/" title="Discutons-en !">non-demande de devis</a>.
  720. </p>
  721. <p>
  722. Je ne traque pas ta navigation mais mon
  723. <abbr title="Alwaysdata, 62 rue Tiquetonne 75002 Paris, +33.184162340">hébergeur</abbr>
  724. conserve des logs d’accès.
  725. </p>
  726. </div>
  727. </footer>
  728. <script type="text/javascript">
  729. ;(_ => {
  730. const jumper = document.getElementById('jumper')
  731. jumper.addEventListener('click', e => {
  732. e.preventDefault()
  733. const anchor = e.target.getAttribute('href')
  734. const targetEl = document.getElementById(anchor.substring(1))
  735. targetEl.scrollIntoView({behavior: 'smooth'})
  736. })
  737. })()
  738. </script>
  739. <script>
  740. /* Service workers */
  741. if (navigator.serviceWorker) {
  742. window.addEventListener('load', function () {
  743. var selector = 'a[href^="/david/cache/"], a[rel=prev], a[rel=next]'
  744. function sendLinks (selector) {
  745. var links = [].slice.call(document.querySelectorAll(selector)).map(function (link) {
  746. return link.getAttribute('href')
  747. })
  748. links.push(location.pathname) // Put the current page in cache too.
  749. navigator.serviceWorker.controller.postMessage({ links: links })
  750. }
  751. navigator.serviceWorker.getRegistration()
  752. .then(function (registration) {
  753. if (!registration || !navigator.serviceWorker.controller) {
  754. return navigator.serviceWorker.register('/serviceworker.js')
  755. .then(navigator.serviceWorker.ready)
  756. .then(function () {
  757. console.log('[ServiceWorker] Ready to go!')
  758. })
  759. .catch(console.error.bind(console))
  760. } else {
  761. console.log('[ServiceWorker] Send links via registration')
  762. sendLinks(selector)
  763. }
  764. })
  765. navigator.serviceWorker.addEventListener('controllerchange', function () {
  766. console.log('[ServiceWorker] Send links via controller change')
  767. sendLinks(selector)
  768. })
  769. navigator.serviceWorker.addEventListener('message', function (event) {
  770. var link = document.querySelector('a[href="' + event.data.link + '"]')
  771. if (event.data.status && link) {
  772. link.style.backgroundColor = '#2d7474'
  773. link.style.color = '#f0f0ea'
  774. link.setAttribute('title', 'En cache pour consultation sans connexion')
  775. }
  776. })
  777. })
  778. } else {
  779. console.warn('[ServiceWorker] No cache for old browsers.')
  780. }
  781. </script>