[{"data":1,"prerenderedAt":2859},["ShallowReactive",2],{"author-raphael-sauget":3,"author-articles-raphael-sauget":22,"authors":2543},{"id":4,"title":5,"body":6,"description":10,"extension":13,"meta":14,"name":15,"navigation":16,"path":17,"readingTime":18,"seo":19,"stem":20,"__hash__":21},"authors\u002Fauthors\u002Fraphael-sauget.md","Engineering Manager",{"type":7,"value":8,"toc":9},"minimark",[],{"title":10,"searchDepth":11,"depth":11,"links":12},"",2,[],"md",{},"Raphaël Sauget",true,"\u002Fauthors\u002Fraphael-sauget",1,{"title":5,"description":10},"authors\u002Fraphael-sauget","Uri9bcq0QDuxRA0PbBoNtu7p_5L3dALu4kzcXVW0xyM",[23],{"id":24,"title":25,"author":26,"body":27,"date":2533,"description":2534,"extension":13,"lang":2535,"meta":2536,"navigation":16,"path":2537,"published":16,"readingTime":281,"seo":2538,"stem":2539,"tags":2540,"__hash__":2542},"articles\u002Farticles\u002F2022-05-12-comment-betonner-lintegration-dune-api.md","Comment bétonner l'intégration d'une API","raphael-sauget",{"type":7,"value":28,"toc":2524},[29,33,36,39,42,45,48,51,54,57,69,74,368,371,374,377,380,384,387,394,614,617,620,626,637,640,644,647,662,929,942,945,948,951,955,958,971,979,998,1001,1298,1301,1305,1316,1319,1322,1328,1335,1346,1360,1896,1901,1904,1910,1913,1917,1927,1930,1933,1946,1949,1960,1963,2486,2492,2495,2498,2501,2505,2514,2517,2520],[30,31,32],"p",{},"Quand on développe une application d’une certaine taille, on fait généralement appel à des solutions\ntierces pour certaines fonctionnalités qui ne sont pas “coeur de métier”, ou tout simplement pour\ndes besoins annexes à l’application (CRM et compagnie).",[30,34,35],{},"Et si tout va bien, cette solution va nous fournir des API pour échanger avec elle.",[30,37,38],{},"Parfois ces API fonctionnent parfaitement, et tout le monde est content. Spoiler alert : c’est loin\nd’être toujours le cas !",[30,40,41],{},"Le but n’étant de taper sur personne, nous allons imaginer une API fictive, qui fonctionnera plus ou\nmoins bien pour démontrer ce qui m’arrange.",[30,43,44],{},"Voilà ce qui va se passer :",[30,46,47],{},"On dispose d’une liste de villes pour lesquelles on a besoin de récupérer la météo.",[30,49,50],{},"On va aller de l’implémentation la plus simple à la plus robuste.",[30,52,53],{},"Si vous maîtrisez JavaScript, vous devriez connaître au moins jusqu’au niveau 2.",[30,55,56],{},"Mais j’ai bon espoir de vous faire découvrir des outils sympa dans la suite, restez jusqu’au bout\n(le 7ème va vous étonner).",[30,58,59,60,66],{},"Si vous voulez tester au fil de l’eau, tous les exemples sont disponibles ici :\n",[61,62],"a",{"href":63,"rel":64},"https:\u002F\u002Freplit.com\u002F@rsauget\u002Fblog-api",[65],"nofollow",[61,67,63],{"href":63,"rel":68},[65],[70,71,73],"h2",{"id":72},"niveau-0-naïf","Niveau 0 - naïf",[75,76,80],"pre",{"className":77,"code":78,"language":79,"meta":10,"style":10},"language-ts shiki shiki-themes github-light github-dark","import _ from \"lodash\";\n\nasync function getWeather({ city }) {\n  \u002F\u002F On ajoute un peu de délai pour se rendre compte de l'impact\n  await new Promise((resolve) => setTimeout(resolve, 1000));\n  return _.sample([\"Soleil\", \"Nuages\", \"Pluie\", \"Neige\"]);\n}\n\nconst cities = [\"Lyon\", \"Montpellier\", \"Paris\"];\n\nconst results = {};\n\nfor (const city of cities) {\n  results[city] = await getWeather({ city });\n}\n\nconsole.log(results);\n\u002F\u002F {\n\u002F\u002F   Lyon: 'Soleil',\n\u002F\u002F   Montpellier: 'Soleil',\n\u002F\u002F   Paris: 'Pluie',\n\u002F\u002F }\n","ts",[81,82,83,105,110,133,140,177,214,220,225,256,261,274,279,299,316,321,326,338,344,350,356,362],"code",{"__ignoreMap":10},[84,85,87,91,95,98,102],"span",{"class":86,"line":18},"line",[84,88,90],{"class":89},"szBVR","import",[84,92,94],{"class":93},"sVt8B"," _ ",[84,96,97],{"class":89},"from",[84,99,101],{"class":100},"sZZnC"," \"lodash\"",[84,103,104],{"class":93},";\n",[84,106,107],{"class":86,"line":11},[84,108,109],{"emptyLinePlaceholder":16},"\n",[84,111,113,116,119,123,126,130],{"class":86,"line":112},3,[84,114,115],{"class":89},"async",[84,117,118],{"class":89}," function",[84,120,122],{"class":121},"sScJk"," getWeather",[84,124,125],{"class":93},"({ ",[84,127,129],{"class":128},"s4XuR","city",[84,131,132],{"class":93}," }) {\n",[84,134,136],{"class":86,"line":135},4,[84,137,139],{"class":138},"sJ8bj","  \u002F\u002F On ajoute un peu de délai pour se rendre compte de l'impact\n",[84,141,143,146,149,153,156,159,162,165,168,171,174],{"class":86,"line":142},5,[84,144,145],{"class":89},"  await",[84,147,148],{"class":89}," new",[84,150,152],{"class":151},"sj4cs"," Promise",[84,154,155],{"class":93},"((",[84,157,158],{"class":128},"resolve",[84,160,161],{"class":93},") ",[84,163,164],{"class":89},"=>",[84,166,167],{"class":121}," setTimeout",[84,169,170],{"class":93},"(resolve, ",[84,172,173],{"class":151},"1000",[84,175,176],{"class":93},"));\n",[84,178,180,183,186,189,192,195,198,201,203,206,208,211],{"class":86,"line":179},6,[84,181,182],{"class":89},"  return",[84,184,185],{"class":93}," _.",[84,187,188],{"class":121},"sample",[84,190,191],{"class":93},"([",[84,193,194],{"class":100},"\"Soleil\"",[84,196,197],{"class":93},", ",[84,199,200],{"class":100},"\"Nuages\"",[84,202,197],{"class":93},[84,204,205],{"class":100},"\"Pluie\"",[84,207,197],{"class":93},[84,209,210],{"class":100},"\"Neige\"",[84,212,213],{"class":93},"]);\n",[84,215,217],{"class":86,"line":216},7,[84,218,219],{"class":93},"}\n",[84,221,223],{"class":86,"line":222},8,[84,224,109],{"emptyLinePlaceholder":16},[84,226,228,231,234,237,240,243,245,248,250,253],{"class":86,"line":227},9,[84,229,230],{"class":89},"const",[84,232,233],{"class":151}," cities",[84,235,236],{"class":89}," =",[84,238,239],{"class":93}," [",[84,241,242],{"class":100},"\"Lyon\"",[84,244,197],{"class":93},[84,246,247],{"class":100},"\"Montpellier\"",[84,249,197],{"class":93},[84,251,252],{"class":100},"\"Paris\"",[84,254,255],{"class":93},"];\n",[84,257,259],{"class":86,"line":258},10,[84,260,109],{"emptyLinePlaceholder":16},[84,262,264,266,269,271],{"class":86,"line":263},11,[84,265,230],{"class":89},[84,267,268],{"class":151}," results",[84,270,236],{"class":89},[84,272,273],{"class":93}," {};\n",[84,275,277],{"class":86,"line":276},12,[84,278,109],{"emptyLinePlaceholder":16},[84,280,282,285,288,290,293,296],{"class":86,"line":281},13,[84,283,284],{"class":89},"for",[84,286,287],{"class":93}," (",[84,289,230],{"class":89},[84,291,292],{"class":151}," city",[84,294,295],{"class":89}," of",[84,297,298],{"class":93}," cities) {\n",[84,300,302,305,308,311,313],{"class":86,"line":301},14,[84,303,304],{"class":93},"  results[city] ",[84,306,307],{"class":89},"=",[84,309,310],{"class":89}," await",[84,312,122],{"class":121},[84,314,315],{"class":93},"({ city });\n",[84,317,319],{"class":86,"line":318},15,[84,320,219],{"class":93},[84,322,324],{"class":86,"line":323},16,[84,325,109],{"emptyLinePlaceholder":16},[84,327,329,332,335],{"class":86,"line":328},17,[84,330,331],{"class":93},"console.",[84,333,334],{"class":121},"log",[84,336,337],{"class":93},"(results);\n",[84,339,341],{"class":86,"line":340},18,[84,342,343],{"class":138},"\u002F\u002F {\n",[84,345,347],{"class":86,"line":346},19,[84,348,349],{"class":138},"\u002F\u002F   Lyon: 'Soleil',\n",[84,351,353],{"class":86,"line":352},20,[84,354,355],{"class":138},"\u002F\u002F   Montpellier: 'Soleil',\n",[84,357,359],{"class":86,"line":358},21,[84,360,361],{"class":138},"\u002F\u002F   Paris: 'Pluie',\n",[84,363,365],{"class":86,"line":364},22,[84,366,367],{"class":138},"\u002F\u002F }\n",[30,369,370],{},"On a ici une première version très simple : on boucle sur les villes demandées, et pour chacune on\nva récupérer le temps qu’il y fait. On agrège les résultats dans un objet.",[30,372,373],{},"Dans un monde parfait, cette solution pourrait convenir.",[30,375,376],{},"En pratique, vous aurez surement noté plein de défauts : c’est bien le but, et d’ici quelques\nminutes j’espère que nous en aurons traité la majorité !",[30,378,379],{},"Le premier défaut de cette version naïve est d’effectuer une requête après l’autre, de façon\nséquentielle, comme si on avait besoin de la météo de la première ville avant de demander la météo\nde la seconde. On sent qu’il y a mieux à faire.",[70,381,383],{"id":382},"version-1-exécution-parallèle","Version 1 - exécution “parallèle”",[30,385,386],{},"Le but n’étant pas de rentrer dans le détail de la gestion de l’asynchronicité en Javascript, vous\nme pardonnerez le raccourci de parler d’exécution parallèle.",[30,388,389,390,393],{},"On va donc utiliser un ",[81,391,392],{},"Promise.all"," pour paralléliser les appels à notre service externe :",[75,395,397],{"className":77,"code":396,"language":79,"meta":10,"style":10},"import _ from \"lodash\";\n\nasync function getWeather({ city }) {\n  await new Promise((resolve) => setTimeout(resolve, 1000));\n  return _.sample([\"Soleil\", \"Nuages\", \"Pluie\", \"Neige\"]);\n}\n\nconst cities = [\"Lyon\", \"Montpellier\", \"Paris\"];\n\n\u002F\u002F On construit l'objet résultant à partir de paires [clé, valeur]\nconst results = Object.fromEntries(\n  await Promise.all(cities.map(async (city) => [city, await getWeather({ city })])),\n);\n\nconsole.log(results);\n\u002F\u002F {\n\u002F\u002F   Lyon: 'Soleil',\n\u002F\u002F   Montpellier: 'Soleil',\n\u002F\u002F   Paris: 'Pluie',\n\u002F\u002F }\n",[81,398,399,411,415,429,453,479,483,487,509,513,518,535,577,582,586,594,598,602,606,610],{"__ignoreMap":10},[84,400,401,403,405,407,409],{"class":86,"line":18},[84,402,90],{"class":89},[84,404,94],{"class":93},[84,406,97],{"class":89},[84,408,101],{"class":100},[84,410,104],{"class":93},[84,412,413],{"class":86,"line":11},[84,414,109],{"emptyLinePlaceholder":16},[84,416,417,419,421,423,425,427],{"class":86,"line":112},[84,418,115],{"class":89},[84,420,118],{"class":89},[84,422,122],{"class":121},[84,424,125],{"class":93},[84,426,129],{"class":128},[84,428,132],{"class":93},[84,430,431,433,435,437,439,441,443,445,447,449,451],{"class":86,"line":135},[84,432,145],{"class":89},[84,434,148],{"class":89},[84,436,152],{"class":151},[84,438,155],{"class":93},[84,440,158],{"class":128},[84,442,161],{"class":93},[84,444,164],{"class":89},[84,446,167],{"class":121},[84,448,170],{"class":93},[84,450,173],{"class":151},[84,452,176],{"class":93},[84,454,455,457,459,461,463,465,467,469,471,473,475,477],{"class":86,"line":142},[84,456,182],{"class":89},[84,458,185],{"class":93},[84,460,188],{"class":121},[84,462,191],{"class":93},[84,464,194],{"class":100},[84,466,197],{"class":93},[84,468,200],{"class":100},[84,470,197],{"class":93},[84,472,205],{"class":100},[84,474,197],{"class":93},[84,476,210],{"class":100},[84,478,213],{"class":93},[84,480,481],{"class":86,"line":179},[84,482,219],{"class":93},[84,484,485],{"class":86,"line":216},[84,486,109],{"emptyLinePlaceholder":16},[84,488,489,491,493,495,497,499,501,503,505,507],{"class":86,"line":222},[84,490,230],{"class":89},[84,492,233],{"class":151},[84,494,236],{"class":89},[84,496,239],{"class":93},[84,498,242],{"class":100},[84,500,197],{"class":93},[84,502,247],{"class":100},[84,504,197],{"class":93},[84,506,252],{"class":100},[84,508,255],{"class":93},[84,510,511],{"class":86,"line":227},[84,512,109],{"emptyLinePlaceholder":16},[84,514,515],{"class":86,"line":258},[84,516,517],{"class":138},"\u002F\u002F On construit l'objet résultant à partir de paires [clé, valeur]\n",[84,519,520,522,524,526,529,532],{"class":86,"line":263},[84,521,230],{"class":89},[84,523,268],{"class":151},[84,525,236],{"class":89},[84,527,528],{"class":93}," Object.",[84,530,531],{"class":121},"fromEntries",[84,533,534],{"class":93},"(\n",[84,536,537,539,541,544,547,550,553,556,558,560,562,564,566,569,572,574],{"class":86,"line":276},[84,538,145],{"class":89},[84,540,152],{"class":151},[84,542,543],{"class":93},".",[84,545,546],{"class":121},"all",[84,548,549],{"class":93},"(cities.",[84,551,552],{"class":121},"map",[84,554,555],{"class":93},"(",[84,557,115],{"class":89},[84,559,287],{"class":93},[84,561,129],{"class":128},[84,563,161],{"class":93},[84,565,164],{"class":89},[84,567,568],{"class":93}," [city, ",[84,570,571],{"class":89},"await",[84,573,122],{"class":121},[84,575,576],{"class":93},"({ city })])),\n",[84,578,579],{"class":86,"line":281},[84,580,581],{"class":93},");\n",[84,583,584],{"class":86,"line":301},[84,585,109],{"emptyLinePlaceholder":16},[84,587,588,590,592],{"class":86,"line":318},[84,589,331],{"class":93},[84,591,334],{"class":121},[84,593,337],{"class":93},[84,595,596],{"class":86,"line":323},[84,597,343],{"class":138},[84,599,600],{"class":86,"line":328},[84,601,349],{"class":138},[84,603,604],{"class":86,"line":340},[84,605,355],{"class":138},[84,607,608],{"class":86,"line":346},[84,609,361],{"class":138},[84,611,612],{"class":86,"line":352},[84,613,367],{"class":138},[30,615,616],{},"Ca fonctionne bien parce qu’on a trois villes, et que notre API peut gérer 3 requêtes concurrentes\nles doigts dans le nez.",[30,618,619],{},"Essayez d’en mettre 1000 pour voir : il y a de bonnes chances que l’API vous dise gentiment de vous\ncalmer.",[30,621,622,623,543],{},"Dans le meilleur des cas, ce sera une réponse HTTP cordiale : ",[81,624,625],{},"429 - Too Many Requests",[30,627,628,629,632,633,636],{},"Mais ça peut passer par un timeout, une déconnexion impromptue (bonjour ",[81,630,631],{},"ECONNRESET","), une erreur\n",[81,634,635],{},"500",", ou même vous faire bloquer votre IP. Pas génial.",[30,638,639],{},"Pour pallier ce problème, on va limiter le nombre de requêtes concurrentes.",[70,641,643],{"id":642},"version-2-parallèle-mais-pas-trop","Version 2 - parallèle, mais pas trop",[30,645,646],{},"On pourrait traiter les requêtes par paquets de 10 par exemple, mais l’idéal serait une fenêtre\nglissante : on en lance 10, et à chaque fois qu’une termine on en relance une, de sorte qu’on en ait\ntoujours 10 qui tournent.",[30,648,649,650,653,654,658,661],{},"Il y a une petite bibliothèque qui permet précisément de faire ça : ",[81,651,652],{},"p-limit","\n(",[61,655],{"href":656,"rel":657},"https:\u002F\u002Fwww.npmjs.com\u002Fpackage\u002Fp-limit",[65],[61,659,656],{"href":656,"rel":660},[65],")",[75,663,665],{"className":77,"code":664,"language":79,"meta":10,"style":10},"import _ from \"lodash\";\nimport pLimit from \"p-limit\";\n\nasync function getWeather({ city }) {\n  await new Promise((resolve) => setTimeout(resolve, 10));\n  return _.sample([\"Soleil\", \"Nuages\", \"Pluie\", \"Neige\"]);\n}\n\nconst cities = _.range(1000).map((n) => `Ville ${n}`);\n\n\u002F\u002F Vous pouvez jouer avec la limite pour voir l'impact sur le temps d'exécution\nconst limit = pLimit(10);\n\n\u002F\u002F On va wrapper l'appel avec p-limit, qui gèrera quand déclencher la fonction\nconst limitedGetWeather = (city) => limit(async () => [city, await getWeather({ city })]);\n\nconst results = Object.fromEntries(await Promise.all(cities.map(limitedGetWeather)));\n\nconsole.log(results);\n",[81,666,667,679,693,697,711,736,762,766,770,811,815,820,838,842,847,884,888,917,921],{"__ignoreMap":10},[84,668,669,671,673,675,677],{"class":86,"line":18},[84,670,90],{"class":89},[84,672,94],{"class":93},[84,674,97],{"class":89},[84,676,101],{"class":100},[84,678,104],{"class":93},[84,680,681,683,686,688,691],{"class":86,"line":11},[84,682,90],{"class":89},[84,684,685],{"class":93}," pLimit ",[84,687,97],{"class":89},[84,689,690],{"class":100}," \"p-limit\"",[84,692,104],{"class":93},[84,694,695],{"class":86,"line":112},[84,696,109],{"emptyLinePlaceholder":16},[84,698,699,701,703,705,707,709],{"class":86,"line":135},[84,700,115],{"class":89},[84,702,118],{"class":89},[84,704,122],{"class":121},[84,706,125],{"class":93},[84,708,129],{"class":128},[84,710,132],{"class":93},[84,712,713,715,717,719,721,723,725,727,729,731,734],{"class":86,"line":142},[84,714,145],{"class":89},[84,716,148],{"class":89},[84,718,152],{"class":151},[84,720,155],{"class":93},[84,722,158],{"class":128},[84,724,161],{"class":93},[84,726,164],{"class":89},[84,728,167],{"class":121},[84,730,170],{"class":93},[84,732,733],{"class":151},"10",[84,735,176],{"class":93},[84,737,738,740,742,744,746,748,750,752,754,756,758,760],{"class":86,"line":179},[84,739,182],{"class":89},[84,741,185],{"class":93},[84,743,188],{"class":121},[84,745,191],{"class":93},[84,747,194],{"class":100},[84,749,197],{"class":93},[84,751,200],{"class":100},[84,753,197],{"class":93},[84,755,205],{"class":100},[84,757,197],{"class":93},[84,759,210],{"class":100},[84,761,213],{"class":93},[84,763,764],{"class":86,"line":216},[84,765,219],{"class":93},[84,767,768],{"class":86,"line":222},[84,769,109],{"emptyLinePlaceholder":16},[84,771,772,774,776,778,780,783,785,787,790,792,794,797,799,801,804,806,809],{"class":86,"line":227},[84,773,230],{"class":89},[84,775,233],{"class":151},[84,777,236],{"class":89},[84,779,185],{"class":93},[84,781,782],{"class":121},"range",[84,784,555],{"class":93},[84,786,173],{"class":151},[84,788,789],{"class":93},").",[84,791,552],{"class":121},[84,793,155],{"class":93},[84,795,796],{"class":128},"n",[84,798,161],{"class":93},[84,800,164],{"class":89},[84,802,803],{"class":100}," `Ville ${",[84,805,796],{"class":93},[84,807,808],{"class":100},"}`",[84,810,581],{"class":93},[84,812,813],{"class":86,"line":258},[84,814,109],{"emptyLinePlaceholder":16},[84,816,817],{"class":86,"line":263},[84,818,819],{"class":138},"\u002F\u002F Vous pouvez jouer avec la limite pour voir l'impact sur le temps d'exécution\n",[84,821,822,824,827,829,832,834,836],{"class":86,"line":276},[84,823,230],{"class":89},[84,825,826],{"class":151}," limit",[84,828,236],{"class":89},[84,830,831],{"class":121}," pLimit",[84,833,555],{"class":93},[84,835,733],{"class":151},[84,837,581],{"class":93},[84,839,840],{"class":86,"line":281},[84,841,109],{"emptyLinePlaceholder":16},[84,843,844],{"class":86,"line":301},[84,845,846],{"class":138},"\u002F\u002F On va wrapper l'appel avec p-limit, qui gèrera quand déclencher la fonction\n",[84,848,849,851,854,856,858,860,862,864,866,868,870,873,875,877,879,881],{"class":86,"line":318},[84,850,230],{"class":89},[84,852,853],{"class":121}," limitedGetWeather",[84,855,236],{"class":89},[84,857,287],{"class":93},[84,859,129],{"class":128},[84,861,161],{"class":93},[84,863,164],{"class":89},[84,865,826],{"class":121},[84,867,555],{"class":93},[84,869,115],{"class":89},[84,871,872],{"class":93}," () ",[84,874,164],{"class":89},[84,876,568],{"class":93},[84,878,571],{"class":89},[84,880,122],{"class":121},[84,882,883],{"class":93},"({ city })]);\n",[84,885,886],{"class":86,"line":323},[84,887,109],{"emptyLinePlaceholder":16},[84,889,890,892,894,896,898,900,902,904,906,908,910,912,914],{"class":86,"line":328},[84,891,230],{"class":89},[84,893,268],{"class":151},[84,895,236],{"class":89},[84,897,528],{"class":93},[84,899,531],{"class":121},[84,901,555],{"class":93},[84,903,571],{"class":89},[84,905,152],{"class":151},[84,907,543],{"class":93},[84,909,546],{"class":121},[84,911,549],{"class":93},[84,913,552],{"class":121},[84,915,916],{"class":93},"(limitedGetWeather)));\n",[84,918,919],{"class":86,"line":340},[84,920,109],{"emptyLinePlaceholder":16},[84,922,923,925,927],{"class":86,"line":346},[84,924,331],{"class":93},[84,926,334],{"class":121},[84,928,337],{"class":93},[30,930,931,932,934,935,197,938,941],{},"Il y a d’ailleurs toute une famille de bibliothèques autour de ",[81,933,652],{}," : ",[81,936,937],{},"p-all",[81,939,940],{},"p-map","...",[30,943,944],{},"Je vous invite à y jeter un oeil, elles sont bien pratiques et assez légères.",[30,946,947],{},"En jouant sur le nombre de requêtes concurrentes qu’on autorise, on peut arriver à coller à peu près\nà ce que tolère l’API.",[30,949,950],{},"Mais on peut faire mieux.",[70,952,954],{"id":953},"version-3-parallèle-juste-ce-quil-faut","Version 3 - parallèle juste ce qu’il faut",[30,956,957],{},"La plupart des API explicitent leur politique de rate-limit dans leur documentation. Elle est\ngénéralement exprimée en nombre de requêtes par unité de temps, par exemple 10000 requêtes par\nminute.",[30,959,960,961,653,964,968,661],{},"Et bien là encore, une bibliothèque qui gère ça très bien : ",[81,962,963],{},"bottleneck",[61,965],{"href":966,"rel":967},"https:\u002F\u002Fwww.npmjs.com\u002Fpackage\u002Fbottleneck",[65],[61,969,966],{"href":966,"rel":970},[65],[30,972,973,974,197,976,978],{},"Contrairement à ",[81,975,652],{},[81,977,963],{}," est assez costaude, mais gère tout un tas de situations :",[980,981,982,986,989,992,995],"ul",{},[983,984,985],"li",{},"“réservoirs” (on vous redonne 100 requêtes toutes les 10 secondes par exemple)",[983,987,988],{},"groupes : un ensemble de limiteurs similaires identifiés par une clé",[983,990,991],{},"distribué : on peut le connecter à un redis et le limiteur est synchronisé entre plusieurs\nservices",[983,993,994],{},"politique de “débordement” : si on fait beaucoup plus de requêtes que la limite autorisée, les\nappels vont s’accumuler dans la file d’attente ; on peut décider de limiter la taille de cette\nfile et de définir ce qu’on fait quand ça “déborde”",[983,996,997],{},"etc.",[30,999,1000],{},"On peut ainsi traiter à peu près tous les cas de figure. Mais pour aujourd’hui, restons sur\nl’exemple simple de 10000 requêtes par minute.",[75,1002,1004],{"className":77,"code":1003,"language":79,"meta":10,"style":10},"import _ from \"lodash\";\nimport Bottleneck from \"bottleneck\";\n\n\u002F\u002F Vous pouvez jouer avec les limites pour voir l'impact sur le temps d'exécution\nconst limiter = new Bottleneck({\n  \u002F\u002F 10000 requêtes par minute => 6ms entre chaque requête\n  minTime: 6,\n  \u002F\u002F c'est généralement une bonne idée d'aussi limiter la concurrence\n  maxConcurrent: 5,\n});\n\n\u002F\u002F On wrap l'appel à l'API avec bottleneck, pour en limiter le flux\nconst getWeather = limiter.wrap(async ({ city }) => {\n  await new Promise((resolve) => setTimeout(resolve, 10));\n  return _.sample([\"Soleil\", \"Nuages\", \"Pluie\", \"Neige\"]);\n});\n\nconst cities = _.range(1000).map((n) => `Ville ${n}`);\n\nconst results = Object.fromEntries(\n  await Promise.all(cities.map(async (city) => [city, await getWeather({ city })])),\n);\n\nconsole.log(results);\n",[81,1005,1006,1018,1032,1036,1041,1058,1063,1074,1079,1089,1094,1098,1103,1134,1158,1184,1188,1192,1228,1232,1246,1280,1284,1289],{"__ignoreMap":10},[84,1007,1008,1010,1012,1014,1016],{"class":86,"line":18},[84,1009,90],{"class":89},[84,1011,94],{"class":93},[84,1013,97],{"class":89},[84,1015,101],{"class":100},[84,1017,104],{"class":93},[84,1019,1020,1022,1025,1027,1030],{"class":86,"line":11},[84,1021,90],{"class":89},[84,1023,1024],{"class":93}," Bottleneck ",[84,1026,97],{"class":89},[84,1028,1029],{"class":100}," \"bottleneck\"",[84,1031,104],{"class":93},[84,1033,1034],{"class":86,"line":112},[84,1035,109],{"emptyLinePlaceholder":16},[84,1037,1038],{"class":86,"line":135},[84,1039,1040],{"class":138},"\u002F\u002F Vous pouvez jouer avec les limites pour voir l'impact sur le temps d'exécution\n",[84,1042,1043,1045,1048,1050,1052,1055],{"class":86,"line":142},[84,1044,230],{"class":89},[84,1046,1047],{"class":151}," limiter",[84,1049,236],{"class":89},[84,1051,148],{"class":89},[84,1053,1054],{"class":121}," Bottleneck",[84,1056,1057],{"class":93},"({\n",[84,1059,1060],{"class":86,"line":179},[84,1061,1062],{"class":138},"  \u002F\u002F 10000 requêtes par minute => 6ms entre chaque requête\n",[84,1064,1065,1068,1071],{"class":86,"line":216},[84,1066,1067],{"class":93},"  minTime: ",[84,1069,1070],{"class":151},"6",[84,1072,1073],{"class":93},",\n",[84,1075,1076],{"class":86,"line":222},[84,1077,1078],{"class":138},"  \u002F\u002F c'est généralement une bonne idée d'aussi limiter la concurrence\n",[84,1080,1081,1084,1087],{"class":86,"line":227},[84,1082,1083],{"class":93},"  maxConcurrent: ",[84,1085,1086],{"class":151},"5",[84,1088,1073],{"class":93},[84,1090,1091],{"class":86,"line":258},[84,1092,1093],{"class":93},"});\n",[84,1095,1096],{"class":86,"line":263},[84,1097,109],{"emptyLinePlaceholder":16},[84,1099,1100],{"class":86,"line":276},[84,1101,1102],{"class":138},"\u002F\u002F On wrap l'appel à l'API avec bottleneck, pour en limiter le flux\n",[84,1104,1105,1107,1109,1111,1114,1117,1119,1121,1124,1126,1129,1131],{"class":86,"line":281},[84,1106,230],{"class":89},[84,1108,122],{"class":151},[84,1110,236],{"class":89},[84,1112,1113],{"class":93}," limiter.",[84,1115,1116],{"class":121},"wrap",[84,1118,555],{"class":93},[84,1120,115],{"class":89},[84,1122,1123],{"class":93}," ({ ",[84,1125,129],{"class":128},[84,1127,1128],{"class":93}," }) ",[84,1130,164],{"class":89},[84,1132,1133],{"class":93}," {\n",[84,1135,1136,1138,1140,1142,1144,1146,1148,1150,1152,1154,1156],{"class":86,"line":301},[84,1137,145],{"class":89},[84,1139,148],{"class":89},[84,1141,152],{"class":151},[84,1143,155],{"class":93},[84,1145,158],{"class":128},[84,1147,161],{"class":93},[84,1149,164],{"class":89},[84,1151,167],{"class":121},[84,1153,170],{"class":93},[84,1155,733],{"class":151},[84,1157,176],{"class":93},[84,1159,1160,1162,1164,1166,1168,1170,1172,1174,1176,1178,1180,1182],{"class":86,"line":318},[84,1161,182],{"class":89},[84,1163,185],{"class":93},[84,1165,188],{"class":121},[84,1167,191],{"class":93},[84,1169,194],{"class":100},[84,1171,197],{"class":93},[84,1173,200],{"class":100},[84,1175,197],{"class":93},[84,1177,205],{"class":100},[84,1179,197],{"class":93},[84,1181,210],{"class":100},[84,1183,213],{"class":93},[84,1185,1186],{"class":86,"line":323},[84,1187,1093],{"class":93},[84,1189,1190],{"class":86,"line":328},[84,1191,109],{"emptyLinePlaceholder":16},[84,1193,1194,1196,1198,1200,1202,1204,1206,1208,1210,1212,1214,1216,1218,1220,1222,1224,1226],{"class":86,"line":340},[84,1195,230],{"class":89},[84,1197,233],{"class":151},[84,1199,236],{"class":89},[84,1201,185],{"class":93},[84,1203,782],{"class":121},[84,1205,555],{"class":93},[84,1207,173],{"class":151},[84,1209,789],{"class":93},[84,1211,552],{"class":121},[84,1213,155],{"class":93},[84,1215,796],{"class":128},[84,1217,161],{"class":93},[84,1219,164],{"class":89},[84,1221,803],{"class":100},[84,1223,796],{"class":93},[84,1225,808],{"class":100},[84,1227,581],{"class":93},[84,1229,1230],{"class":86,"line":346},[84,1231,109],{"emptyLinePlaceholder":16},[84,1233,1234,1236,1238,1240,1242,1244],{"class":86,"line":352},[84,1235,230],{"class":89},[84,1237,268],{"class":151},[84,1239,236],{"class":89},[84,1241,528],{"class":93},[84,1243,531],{"class":121},[84,1245,534],{"class":93},[84,1247,1248,1250,1252,1254,1256,1258,1260,1262,1264,1266,1268,1270,1272,1274,1276,1278],{"class":86,"line":358},[84,1249,145],{"class":89},[84,1251,152],{"class":151},[84,1253,543],{"class":93},[84,1255,546],{"class":121},[84,1257,549],{"class":93},[84,1259,552],{"class":121},[84,1261,555],{"class":93},[84,1263,115],{"class":89},[84,1265,287],{"class":93},[84,1267,129],{"class":128},[84,1269,161],{"class":93},[84,1271,164],{"class":89},[84,1273,568],{"class":93},[84,1275,571],{"class":89},[84,1277,122],{"class":121},[84,1279,576],{"class":93},[84,1281,1282],{"class":86,"line":364},[84,1283,581],{"class":93},[84,1285,1287],{"class":86,"line":1286},23,[84,1288,109],{"emptyLinePlaceholder":16},[84,1290,1292,1294,1296],{"class":86,"line":1291},24,[84,1293,331],{"class":93},[84,1295,334],{"class":121},[84,1297,337],{"class":93},[30,1299,1300],{},"Grâce à bottleneck on peut être au plus proche du rate limit de l’API, et ainsi aller le plus vite\npossible sans se faire flasher.",[70,1302,1304],{"id":1303},"version-4-gestion-derreur","Version 4 - gestion d’erreur",[30,1306,1307,1308,1312,789],{},"Je crois que c’est La Fontaine qui disait que rien ne sert de courir si c’est pour se gauffrer sur\nla première erreur venue\n(",[61,1309],{"href":1310,"rel":1311},"https:\u002F\u002Ffr.wikipedia.org\u002Fwiki\u002FLe_Li%C3%A8vre_et_la_Tortue_(La_Fontaine)",[65],[61,1313,1315],{"href":1310,"rel":1314},[65],"https:\u002F\u002Ffr.wikipedia.org\u002Fwiki\u002FLe_Lièvre_et_la_Tortue_(La_Fontaine)",[30,1317,1318],{},"Et bien c’est exactement ce qu’on est en train de faire. Si une seule requête échoue, elle entraîne\ntoutes les autres dans sa chute, et on se retrouve sans aucun résultat.",[30,1320,1321],{},"Ça peut être souhaitable vous me direz. Mais supposons pour l’exemple qu’on préfère savoir le temps\nqu’il fait dans 99% des villes plutôt que dans aucune.",[30,1323,1324,1325,1327],{},"En plus, le ",[81,1326,392],{}," a un comportement qui peut surprendre : toutes nos requêtes vont\ns’exécuter, quoi qu’il arrive. Certaines peuvent faire des erreurs, ça n’arrête pas les suivantes\npour autant. Et cerise sur le gâteau, l’erreur qui va remonter au final sera seulement la première\nde la liste.",[30,1329,1330,1331,1334],{},"En résumé, en cas d’erreur il est difficile de savoir exactement ce qui s’est passé et ce qui a\nfonctionné ou pas. Ça va qu’on ne fait que des ",[81,1332,1333],{},"GET",", imaginez si on était en train de passer des\ntransactions bancaires...",[30,1336,1337,1338,1341,1342,1345],{},"On peut mettre un ",[81,1339,1340],{},"try\u002Fcatch"," dans notre fonction ",[81,1343,1344],{},"getWeather",", et traiter chaque erreur\nindividuellement. Ça laisse la possibilité de re-throw l’erreur en cas de panique.",[30,1347,1348,1349,1352,1353,1357,789],{},"Mais si on souhaite gérer les erreurs dans leur ensemble, on a une autre fonction :\n",[81,1350,1351],{},"Promise.allSettled","(disponible à partir de node 12.10.0\n",[61,1354],{"href":1355,"rel":1356},"https:\u002F\u002Fnode.green\u002F#ES2020-features--Promise-allSettled",[65],[61,1358,1355],{"href":1355,"rel":1359},[65],[75,1361,1363],{"className":77,"code":1362,"language":79,"meta":10,"style":10},"import _ from \"lodash\";\nimport Bottleneck from \"bottleneck\";\n\nconst limiter = new Bottleneck({\n  minTime: 6,\n  maxConcurrent: 5,\n});\n\nconst getWeather = limiter.wrap(async ({ city }) => {\n  await new Promise((resolve) => setTimeout(resolve, 10));\n  \u002F\u002F On a 1 chance \u002F 10 d'avoir une erreur\n  if (_.random(10) === 0) throw new Error(\"ECONNRESET\");\n  return _.sample([\"Soleil\", \"Nuages\", \"Pluie\", \"Neige\"]);\n});\n\nconst cities = _.range(1000).map((n) => `Ville ${n}`);\n\n\u002F\u002F On zip les villes avec le résultat des requêtes pour faire des paires [ville, resultat]\nconst results = _.zip(cities, await Promise.allSettled(cities.map((city) => getWeather({ city }))));\n\n\u002F\u002F Chaque résultat de Promise.allSettled est de la forme : { status: 'fulfilled', value: 'Résultat' }\n\u002F\u002F ou { status: 'rejected', reason: 'Erreur' }\n\u002F\u002F Ce qui avec le zip donne un results de la forme :\n\u002F\u002F [\n\u002F\u002F   ['Ville 1', { status: 'fulfilled', value: 'Soleil' }],\n\u002F\u002F   ['Ville 2', { status: 'fulfilled', value: 'Pluie' }],\n\u002F\u002F   ['Ville 3', { status: 'rejected', reason: 'ECONNRESET' }],\n\u002F\u002F   ['Ville 4', { status: 'fulfilled', value: 'Pluie' }],\n\u002F\u002F ]\n\u002F\u002F _.partition va nous séparer la liste des résultats en deux listes, selon que la requête a réussi ou non\nconst [successes, failures] = _.partition(results, [\"1.status\", \"fulfilled\"]);\n\nif (!_.isEmpty(failures)) {\n  console.error(\n    _.chain(failures)\n      .groupBy(\"1.reason\")\n      .mapValues((results) => _.map(results, _.first))\n      .value(),\n  );\n}\n\nconsole.log(_.chain(successes).fromPairs().mapValues(\"value\").value());\n",[81,1364,1365,1377,1389,1393,1407,1415,1423,1427,1431,1457,1481,1486,1526,1552,1556,1560,1596,1600,1605,1647,1651,1656,1661,1666,1671,1677,1683,1689,1695,1701,1707,1745,1750,1770,1781,1793,1810,1834,1845,1851,1856,1861],{"__ignoreMap":10},[84,1366,1367,1369,1371,1373,1375],{"class":86,"line":18},[84,1368,90],{"class":89},[84,1370,94],{"class":93},[84,1372,97],{"class":89},[84,1374,101],{"class":100},[84,1376,104],{"class":93},[84,1378,1379,1381,1383,1385,1387],{"class":86,"line":11},[84,1380,90],{"class":89},[84,1382,1024],{"class":93},[84,1384,97],{"class":89},[84,1386,1029],{"class":100},[84,1388,104],{"class":93},[84,1390,1391],{"class":86,"line":112},[84,1392,109],{"emptyLinePlaceholder":16},[84,1394,1395,1397,1399,1401,1403,1405],{"class":86,"line":135},[84,1396,230],{"class":89},[84,1398,1047],{"class":151},[84,1400,236],{"class":89},[84,1402,148],{"class":89},[84,1404,1054],{"class":121},[84,1406,1057],{"class":93},[84,1408,1409,1411,1413],{"class":86,"line":142},[84,1410,1067],{"class":93},[84,1412,1070],{"class":151},[84,1414,1073],{"class":93},[84,1416,1417,1419,1421],{"class":86,"line":179},[84,1418,1083],{"class":93},[84,1420,1086],{"class":151},[84,1422,1073],{"class":93},[84,1424,1425],{"class":86,"line":216},[84,1426,1093],{"class":93},[84,1428,1429],{"class":86,"line":222},[84,1430,109],{"emptyLinePlaceholder":16},[84,1432,1433,1435,1437,1439,1441,1443,1445,1447,1449,1451,1453,1455],{"class":86,"line":227},[84,1434,230],{"class":89},[84,1436,122],{"class":151},[84,1438,236],{"class":89},[84,1440,1113],{"class":93},[84,1442,1116],{"class":121},[84,1444,555],{"class":93},[84,1446,115],{"class":89},[84,1448,1123],{"class":93},[84,1450,129],{"class":128},[84,1452,1128],{"class":93},[84,1454,164],{"class":89},[84,1456,1133],{"class":93},[84,1458,1459,1461,1463,1465,1467,1469,1471,1473,1475,1477,1479],{"class":86,"line":258},[84,1460,145],{"class":89},[84,1462,148],{"class":89},[84,1464,152],{"class":151},[84,1466,155],{"class":93},[84,1468,158],{"class":128},[84,1470,161],{"class":93},[84,1472,164],{"class":89},[84,1474,167],{"class":121},[84,1476,170],{"class":93},[84,1478,733],{"class":151},[84,1480,176],{"class":93},[84,1482,1483],{"class":86,"line":263},[84,1484,1485],{"class":138},"  \u002F\u002F On a 1 chance \u002F 10 d'avoir une erreur\n",[84,1487,1488,1491,1494,1497,1499,1501,1503,1506,1509,1511,1514,1516,1519,1521,1524],{"class":86,"line":276},[84,1489,1490],{"class":89},"  if",[84,1492,1493],{"class":93}," (_.",[84,1495,1496],{"class":121},"random",[84,1498,555],{"class":93},[84,1500,733],{"class":151},[84,1502,161],{"class":93},[84,1504,1505],{"class":89},"===",[84,1507,1508],{"class":151}," 0",[84,1510,161],{"class":93},[84,1512,1513],{"class":89},"throw",[84,1515,148],{"class":89},[84,1517,1518],{"class":121}," Error",[84,1520,555],{"class":93},[84,1522,1523],{"class":100},"\"ECONNRESET\"",[84,1525,581],{"class":93},[84,1527,1528,1530,1532,1534,1536,1538,1540,1542,1544,1546,1548,1550],{"class":86,"line":281},[84,1529,182],{"class":89},[84,1531,185],{"class":93},[84,1533,188],{"class":121},[84,1535,191],{"class":93},[84,1537,194],{"class":100},[84,1539,197],{"class":93},[84,1541,200],{"class":100},[84,1543,197],{"class":93},[84,1545,205],{"class":100},[84,1547,197],{"class":93},[84,1549,210],{"class":100},[84,1551,213],{"class":93},[84,1553,1554],{"class":86,"line":301},[84,1555,1093],{"class":93},[84,1557,1558],{"class":86,"line":318},[84,1559,109],{"emptyLinePlaceholder":16},[84,1561,1562,1564,1566,1568,1570,1572,1574,1576,1578,1580,1582,1584,1586,1588,1590,1592,1594],{"class":86,"line":323},[84,1563,230],{"class":89},[84,1565,233],{"class":151},[84,1567,236],{"class":89},[84,1569,185],{"class":93},[84,1571,782],{"class":121},[84,1573,555],{"class":93},[84,1575,173],{"class":151},[84,1577,789],{"class":93},[84,1579,552],{"class":121},[84,1581,155],{"class":93},[84,1583,796],{"class":128},[84,1585,161],{"class":93},[84,1587,164],{"class":89},[84,1589,803],{"class":100},[84,1591,796],{"class":93},[84,1593,808],{"class":100},[84,1595,581],{"class":93},[84,1597,1598],{"class":86,"line":328},[84,1599,109],{"emptyLinePlaceholder":16},[84,1601,1602],{"class":86,"line":340},[84,1603,1604],{"class":138},"\u002F\u002F On zip les villes avec le résultat des requêtes pour faire des paires [ville, resultat]\n",[84,1606,1607,1609,1611,1613,1615,1618,1621,1623,1625,1627,1630,1632,1634,1636,1638,1640,1642,1644],{"class":86,"line":346},[84,1608,230],{"class":89},[84,1610,268],{"class":151},[84,1612,236],{"class":89},[84,1614,185],{"class":93},[84,1616,1617],{"class":121},"zip",[84,1619,1620],{"class":93},"(cities, ",[84,1622,571],{"class":89},[84,1624,152],{"class":151},[84,1626,543],{"class":93},[84,1628,1629],{"class":121},"allSettled",[84,1631,549],{"class":93},[84,1633,552],{"class":121},[84,1635,155],{"class":93},[84,1637,129],{"class":128},[84,1639,161],{"class":93},[84,1641,164],{"class":89},[84,1643,122],{"class":121},[84,1645,1646],{"class":93},"({ city }))));\n",[84,1648,1649],{"class":86,"line":352},[84,1650,109],{"emptyLinePlaceholder":16},[84,1652,1653],{"class":86,"line":358},[84,1654,1655],{"class":138},"\u002F\u002F Chaque résultat de Promise.allSettled est de la forme : { status: 'fulfilled', value: 'Résultat' }\n",[84,1657,1658],{"class":86,"line":364},[84,1659,1660],{"class":138},"\u002F\u002F ou { status: 'rejected', reason: 'Erreur' }\n",[84,1662,1663],{"class":86,"line":1286},[84,1664,1665],{"class":138},"\u002F\u002F Ce qui avec le zip donne un results de la forme :\n",[84,1667,1668],{"class":86,"line":1291},[84,1669,1670],{"class":138},"\u002F\u002F [\n",[84,1672,1674],{"class":86,"line":1673},25,[84,1675,1676],{"class":138},"\u002F\u002F   ['Ville 1', { status: 'fulfilled', value: 'Soleil' }],\n",[84,1678,1680],{"class":86,"line":1679},26,[84,1681,1682],{"class":138},"\u002F\u002F   ['Ville 2', { status: 'fulfilled', value: 'Pluie' }],\n",[84,1684,1686],{"class":86,"line":1685},27,[84,1687,1688],{"class":138},"\u002F\u002F   ['Ville 3', { status: 'rejected', reason: 'ECONNRESET' }],\n",[84,1690,1692],{"class":86,"line":1691},28,[84,1693,1694],{"class":138},"\u002F\u002F   ['Ville 4', { status: 'fulfilled', value: 'Pluie' }],\n",[84,1696,1698],{"class":86,"line":1697},29,[84,1699,1700],{"class":138},"\u002F\u002F ]\n",[84,1702,1704],{"class":86,"line":1703},30,[84,1705,1706],{"class":138},"\u002F\u002F _.partition va nous séparer la liste des résultats en deux listes, selon que la requête a réussi ou non\n",[84,1708,1710,1712,1714,1717,1719,1722,1725,1727,1729,1732,1735,1738,1740,1743],{"class":86,"line":1709},31,[84,1711,230],{"class":89},[84,1713,239],{"class":93},[84,1715,1716],{"class":151},"successes",[84,1718,197],{"class":93},[84,1720,1721],{"class":151},"failures",[84,1723,1724],{"class":93},"] ",[84,1726,307],{"class":89},[84,1728,185],{"class":93},[84,1730,1731],{"class":121},"partition",[84,1733,1734],{"class":93},"(results, [",[84,1736,1737],{"class":100},"\"1.status\"",[84,1739,197],{"class":93},[84,1741,1742],{"class":100},"\"fulfilled\"",[84,1744,213],{"class":93},[84,1746,1748],{"class":86,"line":1747},32,[84,1749,109],{"emptyLinePlaceholder":16},[84,1751,1753,1756,1758,1761,1764,1767],{"class":86,"line":1752},33,[84,1754,1755],{"class":89},"if",[84,1757,287],{"class":93},[84,1759,1760],{"class":89},"!",[84,1762,1763],{"class":93},"_.",[84,1765,1766],{"class":121},"isEmpty",[84,1768,1769],{"class":93},"(failures)) {\n",[84,1771,1773,1776,1779],{"class":86,"line":1772},34,[84,1774,1775],{"class":93},"  console.",[84,1777,1778],{"class":121},"error",[84,1780,534],{"class":93},[84,1782,1784,1787,1790],{"class":86,"line":1783},35,[84,1785,1786],{"class":93},"    _.",[84,1788,1789],{"class":121},"chain",[84,1791,1792],{"class":93},"(failures)\n",[84,1794,1796,1799,1802,1804,1807],{"class":86,"line":1795},36,[84,1797,1798],{"class":93},"      .",[84,1800,1801],{"class":121},"groupBy",[84,1803,555],{"class":93},[84,1805,1806],{"class":100},"\"1.reason\"",[84,1808,1809],{"class":93},")\n",[84,1811,1813,1815,1818,1820,1823,1825,1827,1829,1831],{"class":86,"line":1812},37,[84,1814,1798],{"class":93},[84,1816,1817],{"class":121},"mapValues",[84,1819,155],{"class":93},[84,1821,1822],{"class":128},"results",[84,1824,161],{"class":93},[84,1826,164],{"class":89},[84,1828,185],{"class":93},[84,1830,552],{"class":121},[84,1832,1833],{"class":93},"(results, _.first))\n",[84,1835,1837,1839,1842],{"class":86,"line":1836},38,[84,1838,1798],{"class":93},[84,1840,1841],{"class":121},"value",[84,1843,1844],{"class":93},"(),\n",[84,1846,1848],{"class":86,"line":1847},39,[84,1849,1850],{"class":93},"  );\n",[84,1852,1854],{"class":86,"line":1853},40,[84,1855,219],{"class":93},[84,1857,1859],{"class":86,"line":1858},41,[84,1860,109],{"emptyLinePlaceholder":16},[84,1862,1864,1866,1868,1871,1873,1876,1879,1882,1884,1886,1889,1891,1893],{"class":86,"line":1863},42,[84,1865,331],{"class":93},[84,1867,334],{"class":121},[84,1869,1870],{"class":93},"(_.",[84,1872,1789],{"class":121},[84,1874,1875],{"class":93},"(successes).",[84,1877,1878],{"class":121},"fromPairs",[84,1880,1881],{"class":93},"().",[84,1883,1817],{"class":121},[84,1885,555],{"class":93},[84,1887,1888],{"class":100},"\"value\"",[84,1890,789],{"class":93},[84,1892,1841],{"class":121},[84,1894,1895],{"class":93},"());\n",[30,1897,1898,1900],{},[81,1899,1351],{}," nous retourne la description du résultat de l’ensemble de nos requêtes,\nqu’elles aient réussi ou pas. Libre à nous de gérer les succès et erreurs comme on le souhaite.",[30,1902,1903],{},"Maintenant, toutes les erreurs ne se valent pas.",[30,1905,1906,1907,543],{},"Si on demande une ville qui n’existe pas par exemple, on peut se contenter de retourner une météo\n",[81,1908,1909],{},"undefined",[30,1911,1912],{},"Mais si le service est juste momentanément indisponible, on peut vouloir essayer une nouvelle fois.",[70,1914,1916],{"id":1915},"version-5-on-persévère","Version 5 - on persévère",[30,1918,1919,1920,1924,789],{},"Un des grands préceptes Shadok nous enseigne que plus ça rate, plus on a de chance que ça marche\n(",[61,1921],{"href":1922,"rel":1923},"https:\u002F\u002Ffr.wikipedia.org\u002Fwiki\u002FLes_Shadoks",[65],[61,1925,1922],{"href":1922,"rel":1926},[65],[30,1928,1929],{},"Dans notre cas, si l’API nous renvoie une erreur momentanée, ou indéterminée, on va vouloir retenter\nnotre chance, jusqu’à ce que ça retombe en marche.",[30,1931,1932],{},"Vous me voyez venir ?",[30,1934,1935,1936,653,1939,1943,661],{},"Bien vu, j’ai une bibliothèque pour ça : ",[81,1937,1938],{},"async-retry",[61,1940],{"href":1941,"rel":1942},"https:\u002F\u002Fwww.npmjs.com\u002Fpackage\u002Fasync-retry",[65],[61,1944,1941],{"href":1941,"rel":1945},[65],[30,1947,1948],{},"Elle permet de réessayer une requête en erreur, avec de chouettes options :",[980,1950,1951,1954,1957],{},[983,1952,1953],{},"délai entre les tentatives, avec un facteur si on veut l’augmenter de manière exponentielle, et\négalement un facteur aléatoire si on veut",[983,1955,1956],{},"limite en nombre de tentatives et\u002Fou en temps",[983,1958,1959],{},"annuler les tentatives restantes, en cas d’erreur fatale par exemple",[30,1961,1962],{},"On peut ainsi faire quelque chose comme :",[75,1964,1966],{"className":77,"code":1965,"language":79,"meta":10,"style":10},"import _ from \"lodash\";\nimport Bottleneck from \"bottleneck\";\nimport retry from \"async-retry\";\n\nconst limiter = new Bottleneck({\n  minTime: 6,\n  maxConcurrent: 5,\n});\n\nconst getWeather = limiter.wrap(async () => {\n  await new Promise((resolve) => setTimeout(resolve, 10));\n  if (_.random(10) === 0) throw new Error(\"ECONNRESET\");\n  return _.sample([\"Soleil\", \"Nuages\", \"Pluie\", \"Neige\"]);\n});\n\n\u002F\u002F On wrap l'appel avec async-retry, pour la gestion d'erreur\nconst getWeatherRetry = async ({ city }) =>\n  retry(async () => getWeather({ city }), {\n    \u002F\u002F On retente toutes les 100ms dans la limite de 5 fois\n    retries: 5,\n    minTimeout: 100,\n    factor: 1,\n  });\n\nconst cities = _.range(1000).map((n) => `Ville ${n}`);\n\nconst results = _.zip(\n  cities,\n  await Promise.allSettled(cities.map((city) => getWeatherRetry({ city }))),\n);\n\nconst [successes, failures] = _.partition(results, [\"1.status\", \"fulfilled\"]);\n\nif (!_.isEmpty(failures)) {\n  console.error(\n    _.chain(failures)\n      .groupBy(\"1.reason\")\n      .mapValues((results) => _.map(results, _.first))\n      .value(),\n  );\n}\n\nconsole.log(_.chain(successes).fromPairs().mapValues(\"value\").value());\n",[81,1967,1968,1980,1992,2006,2010,2024,2032,2040,2044,2048,2070,2094,2126,2152,2156,2160,2165,2186,2204,2209,2218,2228,2238,2243,2247,2283,2287,2301,2306,2333,2337,2341,2371,2375,2389,2397,2405,2417,2437,2445,2449,2453,2457],{"__ignoreMap":10},[84,1969,1970,1972,1974,1976,1978],{"class":86,"line":18},[84,1971,90],{"class":89},[84,1973,94],{"class":93},[84,1975,97],{"class":89},[84,1977,101],{"class":100},[84,1979,104],{"class":93},[84,1981,1982,1984,1986,1988,1990],{"class":86,"line":11},[84,1983,90],{"class":89},[84,1985,1024],{"class":93},[84,1987,97],{"class":89},[84,1989,1029],{"class":100},[84,1991,104],{"class":93},[84,1993,1994,1996,1999,2001,2004],{"class":86,"line":112},[84,1995,90],{"class":89},[84,1997,1998],{"class":93}," retry ",[84,2000,97],{"class":89},[84,2002,2003],{"class":100}," \"async-retry\"",[84,2005,104],{"class":93},[84,2007,2008],{"class":86,"line":135},[84,2009,109],{"emptyLinePlaceholder":16},[84,2011,2012,2014,2016,2018,2020,2022],{"class":86,"line":142},[84,2013,230],{"class":89},[84,2015,1047],{"class":151},[84,2017,236],{"class":89},[84,2019,148],{"class":89},[84,2021,1054],{"class":121},[84,2023,1057],{"class":93},[84,2025,2026,2028,2030],{"class":86,"line":179},[84,2027,1067],{"class":93},[84,2029,1070],{"class":151},[84,2031,1073],{"class":93},[84,2033,2034,2036,2038],{"class":86,"line":216},[84,2035,1083],{"class":93},[84,2037,1086],{"class":151},[84,2039,1073],{"class":93},[84,2041,2042],{"class":86,"line":222},[84,2043,1093],{"class":93},[84,2045,2046],{"class":86,"line":227},[84,2047,109],{"emptyLinePlaceholder":16},[84,2049,2050,2052,2054,2056,2058,2060,2062,2064,2066,2068],{"class":86,"line":258},[84,2051,230],{"class":89},[84,2053,122],{"class":151},[84,2055,236],{"class":89},[84,2057,1113],{"class":93},[84,2059,1116],{"class":121},[84,2061,555],{"class":93},[84,2063,115],{"class":89},[84,2065,872],{"class":93},[84,2067,164],{"class":89},[84,2069,1133],{"class":93},[84,2071,2072,2074,2076,2078,2080,2082,2084,2086,2088,2090,2092],{"class":86,"line":263},[84,2073,145],{"class":89},[84,2075,148],{"class":89},[84,2077,152],{"class":151},[84,2079,155],{"class":93},[84,2081,158],{"class":128},[84,2083,161],{"class":93},[84,2085,164],{"class":89},[84,2087,167],{"class":121},[84,2089,170],{"class":93},[84,2091,733],{"class":151},[84,2093,176],{"class":93},[84,2095,2096,2098,2100,2102,2104,2106,2108,2110,2112,2114,2116,2118,2120,2122,2124],{"class":86,"line":276},[84,2097,1490],{"class":89},[84,2099,1493],{"class":93},[84,2101,1496],{"class":121},[84,2103,555],{"class":93},[84,2105,733],{"class":151},[84,2107,161],{"class":93},[84,2109,1505],{"class":89},[84,2111,1508],{"class":151},[84,2113,161],{"class":93},[84,2115,1513],{"class":89},[84,2117,148],{"class":89},[84,2119,1518],{"class":121},[84,2121,555],{"class":93},[84,2123,1523],{"class":100},[84,2125,581],{"class":93},[84,2127,2128,2130,2132,2134,2136,2138,2140,2142,2144,2146,2148,2150],{"class":86,"line":281},[84,2129,182],{"class":89},[84,2131,185],{"class":93},[84,2133,188],{"class":121},[84,2135,191],{"class":93},[84,2137,194],{"class":100},[84,2139,197],{"class":93},[84,2141,200],{"class":100},[84,2143,197],{"class":93},[84,2145,205],{"class":100},[84,2147,197],{"class":93},[84,2149,210],{"class":100},[84,2151,213],{"class":93},[84,2153,2154],{"class":86,"line":301},[84,2155,1093],{"class":93},[84,2157,2158],{"class":86,"line":318},[84,2159,109],{"emptyLinePlaceholder":16},[84,2161,2162],{"class":86,"line":323},[84,2163,2164],{"class":138},"\u002F\u002F On wrap l'appel avec async-retry, pour la gestion d'erreur\n",[84,2166,2167,2169,2172,2174,2177,2179,2181,2183],{"class":86,"line":328},[84,2168,230],{"class":89},[84,2170,2171],{"class":121}," getWeatherRetry",[84,2173,236],{"class":89},[84,2175,2176],{"class":89}," async",[84,2178,1123],{"class":93},[84,2180,129],{"class":128},[84,2182,1128],{"class":93},[84,2184,2185],{"class":89},"=>\n",[84,2187,2188,2191,2193,2195,2197,2199,2201],{"class":86,"line":340},[84,2189,2190],{"class":121},"  retry",[84,2192,555],{"class":93},[84,2194,115],{"class":89},[84,2196,872],{"class":93},[84,2198,164],{"class":89},[84,2200,122],{"class":121},[84,2202,2203],{"class":93},"({ city }), {\n",[84,2205,2206],{"class":86,"line":346},[84,2207,2208],{"class":138},"    \u002F\u002F On retente toutes les 100ms dans la limite de 5 fois\n",[84,2210,2211,2214,2216],{"class":86,"line":352},[84,2212,2213],{"class":93},"    retries: ",[84,2215,1086],{"class":151},[84,2217,1073],{"class":93},[84,2219,2220,2223,2226],{"class":86,"line":358},[84,2221,2222],{"class":93},"    minTimeout: ",[84,2224,2225],{"class":151},"100",[84,2227,1073],{"class":93},[84,2229,2230,2233,2236],{"class":86,"line":364},[84,2231,2232],{"class":93},"    factor: ",[84,2234,2235],{"class":151},"1",[84,2237,1073],{"class":93},[84,2239,2240],{"class":86,"line":1286},[84,2241,2242],{"class":93},"  });\n",[84,2244,2245],{"class":86,"line":1291},[84,2246,109],{"emptyLinePlaceholder":16},[84,2248,2249,2251,2253,2255,2257,2259,2261,2263,2265,2267,2269,2271,2273,2275,2277,2279,2281],{"class":86,"line":1673},[84,2250,230],{"class":89},[84,2252,233],{"class":151},[84,2254,236],{"class":89},[84,2256,185],{"class":93},[84,2258,782],{"class":121},[84,2260,555],{"class":93},[84,2262,173],{"class":151},[84,2264,789],{"class":93},[84,2266,552],{"class":121},[84,2268,155],{"class":93},[84,2270,796],{"class":128},[84,2272,161],{"class":93},[84,2274,164],{"class":89},[84,2276,803],{"class":100},[84,2278,796],{"class":93},[84,2280,808],{"class":100},[84,2282,581],{"class":93},[84,2284,2285],{"class":86,"line":1679},[84,2286,109],{"emptyLinePlaceholder":16},[84,2288,2289,2291,2293,2295,2297,2299],{"class":86,"line":1685},[84,2290,230],{"class":89},[84,2292,268],{"class":151},[84,2294,236],{"class":89},[84,2296,185],{"class":93},[84,2298,1617],{"class":121},[84,2300,534],{"class":93},[84,2302,2303],{"class":86,"line":1691},[84,2304,2305],{"class":93},"  cities,\n",[84,2307,2308,2310,2312,2314,2316,2318,2320,2322,2324,2326,2328,2330],{"class":86,"line":1697},[84,2309,145],{"class":89},[84,2311,152],{"class":151},[84,2313,543],{"class":93},[84,2315,1629],{"class":121},[84,2317,549],{"class":93},[84,2319,552],{"class":121},[84,2321,155],{"class":93},[84,2323,129],{"class":128},[84,2325,161],{"class":93},[84,2327,164],{"class":89},[84,2329,2171],{"class":121},[84,2331,2332],{"class":93},"({ city }))),\n",[84,2334,2335],{"class":86,"line":1703},[84,2336,581],{"class":93},[84,2338,2339],{"class":86,"line":1709},[84,2340,109],{"emptyLinePlaceholder":16},[84,2342,2343,2345,2347,2349,2351,2353,2355,2357,2359,2361,2363,2365,2367,2369],{"class":86,"line":1747},[84,2344,230],{"class":89},[84,2346,239],{"class":93},[84,2348,1716],{"class":151},[84,2350,197],{"class":93},[84,2352,1721],{"class":151},[84,2354,1724],{"class":93},[84,2356,307],{"class":89},[84,2358,185],{"class":93},[84,2360,1731],{"class":121},[84,2362,1734],{"class":93},[84,2364,1737],{"class":100},[84,2366,197],{"class":93},[84,2368,1742],{"class":100},[84,2370,213],{"class":93},[84,2372,2373],{"class":86,"line":1752},[84,2374,109],{"emptyLinePlaceholder":16},[84,2376,2377,2379,2381,2383,2385,2387],{"class":86,"line":1772},[84,2378,1755],{"class":89},[84,2380,287],{"class":93},[84,2382,1760],{"class":89},[84,2384,1763],{"class":93},[84,2386,1766],{"class":121},[84,2388,1769],{"class":93},[84,2390,2391,2393,2395],{"class":86,"line":1783},[84,2392,1775],{"class":93},[84,2394,1778],{"class":121},[84,2396,534],{"class":93},[84,2398,2399,2401,2403],{"class":86,"line":1795},[84,2400,1786],{"class":93},[84,2402,1789],{"class":121},[84,2404,1792],{"class":93},[84,2406,2407,2409,2411,2413,2415],{"class":86,"line":1812},[84,2408,1798],{"class":93},[84,2410,1801],{"class":121},[84,2412,555],{"class":93},[84,2414,1806],{"class":100},[84,2416,1809],{"class":93},[84,2418,2419,2421,2423,2425,2427,2429,2431,2433,2435],{"class":86,"line":1836},[84,2420,1798],{"class":93},[84,2422,1817],{"class":121},[84,2424,155],{"class":93},[84,2426,1822],{"class":128},[84,2428,161],{"class":93},[84,2430,164],{"class":89},[84,2432,185],{"class":93},[84,2434,552],{"class":121},[84,2436,1833],{"class":93},[84,2438,2439,2441,2443],{"class":86,"line":1847},[84,2440,1798],{"class":93},[84,2442,1841],{"class":121},[84,2444,1844],{"class":93},[84,2446,2447],{"class":86,"line":1853},[84,2448,1850],{"class":93},[84,2450,2451],{"class":86,"line":1858},[84,2452,219],{"class":93},[84,2454,2455],{"class":86,"line":1863},[84,2456,109],{"emptyLinePlaceholder":16},[84,2458,2460,2462,2464,2466,2468,2470,2472,2474,2476,2478,2480,2482,2484],{"class":86,"line":2459},43,[84,2461,331],{"class":93},[84,2463,334],{"class":121},[84,2465,1870],{"class":93},[84,2467,1789],{"class":121},[84,2469,1875],{"class":93},[84,2471,1878],{"class":121},[84,2473,1881],{"class":93},[84,2475,1817],{"class":121},[84,2477,555],{"class":93},[84,2479,1888],{"class":100},[84,2481,789],{"class":93},[84,2483,1841],{"class":121},[84,2485,1895],{"class":93},[30,2487,2488,2489,2491],{},"Attention toutefois : c’est sympa de réessayer les requêtes jusqu’à ce que ça marche, comme un\nbourrin. Quand on ",[81,2490,1333],{}," de la donnée comme ici, il ne peut rien se passer de trop gênant. Si la\nrequête fait des modifications en revanche, ça n’est pas toujours une bonne idée.",[30,2493,2494],{},"Prenons un exemple au hasard : imaginez que Netflix fasse ça sur la requête de paiement de votre\nabonnement, et que celle-ci soit rejouée 10 fois. Eh ben ça va pas faire une soirée très “chill”,\ncomme disent les jeunes.",[30,2496,2497],{},"Il faut donc toujours se poser la question de l’impact de faire la requête plusieurs fois sur le\nfonctionnement du système, avant de mettre en place une telle logique.",[30,2499,2500],{},"C’est aussi sympa pour l’API de mettre un délai exponentiel. Si elle est sous l’eau, vous lui\nlaisserez ainsi une chance de reprendre ses esprits avant de vous répondre.",[70,2502,2504],{"id":2503},"conclusion","Conclusion",[30,2506,2507,2508,2510,2511,2513],{},"En combinant une logique de rate-limit avec une bonne gestion d’erreur, comme on a pu le faire avec\nle combo gagnant ",[81,2509,963],{}," + ",[81,2512,1938],{},", on arrive à un code relativement tout-terrain.",[30,2515,2516],{},"On l’utilise chez Indy pour certaines synchronisations un peu capricieuses, et ça tourne comme une\nhorloge.",[30,2518,2519],{},"Et pour finir sur une note plus sérieuse, gardez à l’esprit qu’il y a aussi des dev derrière les API\nsur lesquelles on aime râler, et que c’est très difficile, voire impossible, de fournir un service\nparfaitement fiable. Soyez compréhensifs, et raisonnables dans vos intégrations, ne faites pas les\nbourrins 😉",[2521,2522,2523],"style",{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":10,"searchDepth":11,"depth":11,"links":2525},[2526,2527,2528,2529,2530,2531,2532],{"id":72,"depth":11,"text":73},{"id":382,"depth":11,"text":383},{"id":642,"depth":11,"text":643},{"id":953,"depth":11,"text":954},{"id":1303,"depth":11,"text":1304},{"id":1915,"depth":11,"text":1916},{"id":2503,"depth":11,"text":2504},"2022-05-12","Quand on développe une application d’une certaine taille, on fait généralement appel à des solutions tierces pour certaines fonctionnalités qui ne sont pas “coeur de métier”","fr",{},"\u002Farticles\u002F2022-05-12-comment-betonner-lintegration-dune-api",{"title":25,"description":2534},"articles\u002F2022-05-12-comment-betonner-lintegration-dune-api",[2541],"Tech","Gk50eQTnzzFww7DGNTtv5ubljru7umDJF4ZDYQhS2Lk",[2544,2557,2569,2581,2594,2606,2618,2631,2644,2657,2669,2681,2694,2706,2718,2730,2742,2754,2766,2773,2786,2798,2810,2823,2835,2847],{"id":2545,"title":2546,"body":2547,"description":10,"extension":13,"meta":2551,"name":2552,"navigation":16,"path":2553,"readingTime":18,"seo":2554,"stem":2555,"__hash__":2556},"authors\u002Fauthors\u002Falexandre-guillon.md","Software Engineer",{"type":7,"value":2548,"toc":2549},[],{"title":10,"searchDepth":11,"depth":11,"links":2550},[],{},"Alexandre Guillon","\u002Fauthors\u002Falexandre-guillon",{"title":2546,"description":10},"authors\u002Falexandre-guillon","4tf48mjyjFNqItOHaulICbrjeCyMag1o6801uHeTz98",{"id":2558,"title":2546,"body":2559,"description":10,"extension":13,"meta":2563,"name":2564,"navigation":16,"path":2565,"readingTime":18,"seo":2566,"stem":2567,"__hash__":2568},"authors\u002Fauthors\u002Falexis-ablain.md",{"type":7,"value":2560,"toc":2561},[],{"title":10,"searchDepth":11,"depth":11,"links":2562},[],{},"Alexis Ablain","\u002Fauthors\u002Falexis-ablain",{"title":2546,"description":10},"authors\u002Falexis-ablain","_SIAtB7f-39e5t3GiJof81NP47s6MGo2n4gaHkTy1uQ",{"id":2570,"title":5,"body":2571,"description":10,"extension":13,"meta":2575,"name":2576,"navigation":16,"path":2577,"readingTime":18,"seo":2578,"stem":2579,"__hash__":2580},"authors\u002Fauthors\u002Faxel-shaita.md",{"type":7,"value":2572,"toc":2573},[],{"title":10,"searchDepth":11,"depth":11,"links":2574},[],{},"Axel Shaïta","\u002Fauthors\u002Faxel-shaita",{"title":5,"description":10},"authors\u002Faxel-shaita","fK0argUhsBkWLjpTAhY13oYLVzQthcEYkCEdtHWmIgE",{"id":2582,"title":2583,"body":2584,"description":10,"extension":13,"meta":2588,"name":2589,"navigation":16,"path":2590,"readingTime":18,"seo":2591,"stem":2592,"__hash__":2593},"authors\u002Fauthors\u002Fbaptiste-faure.md","Head of Talent Acquisition",{"type":7,"value":2585,"toc":2586},[],{"title":10,"searchDepth":11,"depth":11,"links":2587},[],{},"Baptiste Faure","\u002Fauthors\u002Fbaptiste-faure",{"title":2583,"description":10},"authors\u002Fbaptiste-faure","ELisToYtcgHmgdVWZkCclTPV6exZtfyXqhpx1jjbJHs",{"id":2595,"title":2546,"body":2596,"description":10,"extension":13,"meta":2600,"name":2601,"navigation":16,"path":2602,"readingTime":18,"seo":2603,"stem":2604,"__hash__":2605},"authors\u002Fauthors\u002Fbenjamin-bouillot.md",{"type":7,"value":2597,"toc":2598},[],{"title":10,"searchDepth":11,"depth":11,"links":2599},[],{},"Benjamin Bouillot","\u002Fauthors\u002Fbenjamin-bouillot",{"title":2546,"description":10},"authors\u002Fbenjamin-bouillot","tbhCFZyfTt7ZM5b5YgqQ2nhgnSTl8BweaQQryc87fHo",{"id":2607,"title":5,"body":2608,"description":10,"extension":13,"meta":2612,"name":2613,"navigation":16,"path":2614,"readingTime":18,"seo":2615,"stem":2616,"__hash__":2617},"authors\u002Fauthors\u002Fcedric-nicoloso.md",{"type":7,"value":2609,"toc":2610},[],{"title":10,"searchDepth":11,"depth":11,"links":2611},[],{},"Cédric Nicoloso","\u002Fauthors\u002Fcedric-nicoloso",{"title":5,"description":10},"authors\u002Fcedric-nicoloso","ibSoh4VZYiWYTuLOnZTedaAfcnvet1Q9H7ogW0LgorY",{"id":2619,"title":2620,"body":2621,"description":10,"extension":13,"meta":2625,"name":2626,"navigation":16,"path":2627,"readingTime":18,"seo":2628,"stem":2629,"__hash__":2630},"authors\u002Fauthors\u002Fdavid-touzet.md","Staff Engineer",{"type":7,"value":2622,"toc":2623},[],{"title":10,"searchDepth":11,"depth":11,"links":2624},[],{},"David Touzet","\u002Fauthors\u002Fdavid-touzet",{"title":2620,"description":10},"authors\u002Fdavid-touzet","dHWwnQxb1Ubt-WwXWEODGEo9AFoq1cJUhfg3kdnYSBM",{"id":2632,"title":2633,"body":2634,"description":10,"extension":13,"meta":2638,"name":2639,"navigation":16,"path":2640,"readingTime":18,"seo":2641,"stem":2642,"__hash__":2643},"authors\u002Fauthors\u002Feloise-chizat.md","Data Engineer",{"type":7,"value":2635,"toc":2636},[],{"title":10,"searchDepth":11,"depth":11,"links":2637},[],{},"Eloïse Chizat","\u002Fauthors\u002Feloise-chizat",{"title":2633,"description":10},"authors\u002Feloise-chizat","Utd72Vm9qT4hh2ZbFi6a2_nXw5Wb494Ed_HL1ra5yw8",{"id":2645,"title":2646,"body":2647,"description":10,"extension":13,"meta":2651,"name":2652,"navigation":16,"path":2653,"readingTime":18,"seo":2654,"stem":2655,"__hash__":2656},"authors\u002Fauthors\u002Femmanuel-auclair.md","Staff engineer",{"type":7,"value":2648,"toc":2649},[],{"title":10,"searchDepth":11,"depth":11,"links":2650},[],{},"Emmanuel Auclair","\u002Fauthors\u002Femmanuel-auclair",{"title":2646,"description":10},"authors\u002Femmanuel-auclair","MtsA8THNLEn0dTtYEIQaGwDuf7MjQL55IOeei5gugEg",{"id":2658,"title":2546,"body":2659,"description":10,"extension":13,"meta":2663,"name":2664,"navigation":16,"path":2665,"readingTime":18,"seo":2666,"stem":2667,"__hash__":2668},"authors\u002Fauthors\u002Fhoreb-parraud.md",{"type":7,"value":2660,"toc":2661},[],{"title":10,"searchDepth":11,"depth":11,"links":2662},[],{},"Horeb Parraud","\u002Fauthors\u002Fhoreb-parraud",{"title":2546,"description":10},"authors\u002Fhoreb-parraud","ajjsnUX4ohZI-ghMdbb92q_taWDkKXVZSLZXoAeLQtg",{"id":2670,"title":5,"body":2671,"description":10,"extension":13,"meta":2675,"name":2676,"navigation":16,"path":2677,"readingTime":18,"seo":2678,"stem":2679,"__hash__":2680},"authors\u002Fauthors\u002Fhugo-contreras.md",{"type":7,"value":2672,"toc":2673},[],{"title":10,"searchDepth":11,"depth":11,"links":2674},[],{},"Hugo Contreras","\u002Fauthors\u002Fhugo-contreras",{"title":5,"description":10},"authors\u002Fhugo-contreras","2nc3VMu9ASq9Z6Pwx2-7-Ye991Pww4p-UEDBQFfjF-Q",{"id":2682,"title":2683,"body":2684,"description":10,"extension":13,"meta":2688,"name":2689,"navigation":16,"path":2690,"readingTime":18,"seo":2691,"stem":2692,"__hash__":2693},"authors\u002Fauthors\u002Fjulien-tassin.md","Head of Engineering",{"type":7,"value":2685,"toc":2686},[],{"title":10,"searchDepth":11,"depth":11,"links":2687},[],{},"Julien Tassin","\u002Fauthors\u002Fjulien-tassin",{"title":2683,"description":10},"authors\u002Fjulien-tassin","iUIHI7SITje38Jh9X9uvYs4-VsHx4eCdt6hAlyLFG_o",{"id":2695,"title":2546,"body":2696,"description":10,"extension":13,"meta":2700,"name":2701,"navigation":16,"path":2702,"readingTime":18,"seo":2703,"stem":2704,"__hash__":2705},"authors\u002Fauthors\u002Flaurent-renard.md",{"type":7,"value":2697,"toc":2698},[],{"title":10,"searchDepth":11,"depth":11,"links":2699},[],{},"Laurent Renard","\u002Fauthors\u002Flaurent-renard",{"title":2546,"description":10},"authors\u002Flaurent-renard","5BP7Ed-pt1SQHjh0UJ1XUrlLTcdlFaDoKBCP4deHq8A",{"id":2707,"title":2546,"body":2708,"description":10,"extension":13,"meta":2712,"name":2713,"navigation":16,"path":2714,"readingTime":18,"seo":2715,"stem":2716,"__hash__":2717},"authors\u002Fauthors\u002Fleo-martin.md",{"type":7,"value":2709,"toc":2710},[],{"title":10,"searchDepth":11,"depth":11,"links":2711},[],{},"Léo Martin","\u002Fauthors\u002Fleo-martin",{"title":2546,"description":10},"authors\u002Fleo-martin","eYxCHkRgbGDV7shKdTA9s7Tu0zGV4yDGFoKR5MHQntY",{"id":2719,"title":2546,"body":2720,"description":10,"extension":13,"meta":2724,"name":2725,"navigation":16,"path":2726,"readingTime":18,"seo":2727,"stem":2728,"__hash__":2729},"authors\u002Fauthors\u002Floic-bousquet.md",{"type":7,"value":2721,"toc":2722},[],{"title":10,"searchDepth":11,"depth":11,"links":2723},[],{},"Loïc Bousquet","\u002Fauthors\u002Floic-bousquet",{"title":2546,"description":10},"authors\u002Floic-bousquet","ko12qZwiGL8XNjAoy9oWypPkIjr29Pbq7vhdtgldqeQ",{"id":2731,"title":2546,"body":2732,"description":10,"extension":13,"meta":2736,"name":2737,"navigation":16,"path":2738,"readingTime":18,"seo":2739,"stem":2740,"__hash__":2741},"authors\u002Fauthors\u002Floic-poullain.md",{"type":7,"value":2733,"toc":2734},[],{"title":10,"searchDepth":11,"depth":11,"links":2735},[],{},"Loïc Poullain","\u002Fauthors\u002Floic-poullain",{"title":2546,"description":10},"authors\u002Floic-poullain","oRIyJhFRTqxy5dLCYQ2OnYZ1DB-gLDUM-85vTSYuTF0",{"id":2743,"title":2633,"body":2744,"description":10,"extension":13,"meta":2748,"name":2749,"navigation":16,"path":2750,"readingTime":18,"seo":2751,"stem":2752,"__hash__":2753},"authors\u002Fauthors\u002Fmaud-lelu.md",{"type":7,"value":2745,"toc":2746},[],{"title":10,"searchDepth":11,"depth":11,"links":2747},[],{},"Maud Lélu","\u002Fauthors\u002Fmaud-lelu",{"title":2633,"description":10},"authors\u002Fmaud-lelu","MMbsCKuE41OMHusrl12FIEsI-Trx7l8Nn_ANhvj2_y4",{"id":2755,"title":5,"body":2756,"description":10,"extension":13,"meta":2760,"name":2761,"navigation":16,"path":2762,"readingTime":18,"seo":2763,"stem":2764,"__hash__":2765},"authors\u002Fauthors\u002Fnicolas-poirier.md",{"type":7,"value":2757,"toc":2758},[],{"title":10,"searchDepth":11,"depth":11,"links":2759},[],{},"Nicolas Poirier","\u002Fauthors\u002Fnicolas-poirier",{"title":5,"description":10},"authors\u002Fnicolas-poirier","dXrJkYo8az4SN_D23aYc3fQ7z8s1dR2a0lt1ogjAjJs",{"id":4,"title":5,"body":2767,"description":10,"extension":13,"meta":2771,"name":15,"navigation":16,"path":17,"readingTime":18,"seo":2772,"stem":20,"__hash__":21},{"type":7,"value":2768,"toc":2769},[],{"title":10,"searchDepth":11,"depth":11,"links":2770},[],{},{"title":5,"description":10},{"id":2774,"title":2775,"body":2776,"description":10,"extension":13,"meta":2780,"name":2781,"navigation":16,"path":2782,"readingTime":18,"seo":2783,"stem":2784,"__hash__":2785},"authors\u002Fauthors\u002Fromain-koenig.md","Co-funder & Head of innovation",{"type":7,"value":2777,"toc":2778},[],{"title":10,"searchDepth":11,"depth":11,"links":2779},[],{},"Romain Koenig","\u002Fauthors\u002Fromain-koenig",{"title":2775,"description":10},"authors\u002Fromain-koenig","uyS8--eG2_ezyqRABcJnMJmQKKuSArhPWd14aUvFeEw",{"id":2787,"title":5,"body":2788,"description":10,"extension":13,"meta":2792,"name":2793,"navigation":16,"path":2794,"readingTime":18,"seo":2795,"stem":2796,"__hash__":2797},"authors\u002Fauthors\u002Fromaric-juniet.md",{"type":7,"value":2789,"toc":2790},[],{"title":10,"searchDepth":11,"depth":11,"links":2791},[],{},"Romaric Juniet","\u002Fauthors\u002Fromaric-juniet",{"title":5,"description":10},"authors\u002Fromaric-juniet","4Zb2artgT-eo-PHLXi3xi4d5t7s6PfhUxeSfXIikSUY",{"id":2799,"title":2546,"body":2800,"description":10,"extension":13,"meta":2804,"name":2805,"navigation":16,"path":2806,"readingTime":18,"seo":2807,"stem":2808,"__hash__":2809},"authors\u002Fauthors\u002Fstanyslas-bres.md",{"type":7,"value":2801,"toc":2802},[],{"title":10,"searchDepth":11,"depth":11,"links":2803},[],{},"Stanyslas Bres","\u002Fauthors\u002Fstanyslas-bres",{"title":2546,"description":10},"authors\u002Fstanyslas-bres","Xa0SahETuiN4q1jrmR2ych3moAqcZ2LbU7vSfEt2RuU",{"id":2811,"title":2812,"body":2813,"description":10,"extension":13,"meta":2817,"name":2818,"navigation":16,"path":2819,"readingTime":18,"seo":2820,"stem":2821,"__hash__":2822},"authors\u002Fauthors\u002Ftalent-acquisition.md","Talent Acquisition",{"type":7,"value":2814,"toc":2815},[],{"title":10,"searchDepth":11,"depth":11,"links":2816},[],{},"Équipe Talent Acquisition","\u002Fauthors\u002Ftalent-acquisition",{"description":10},"authors\u002Ftalent-acquisition","doDfE76txftQ4wIiKjJoDmSpyzSKk0tzlgVAp6-opAY",{"id":2824,"title":2546,"body":2825,"description":10,"extension":13,"meta":2829,"name":2830,"navigation":16,"path":2831,"readingTime":18,"seo":2832,"stem":2833,"__hash__":2834},"authors\u002Fauthors\u002Fvictor-borg.md",{"type":7,"value":2826,"toc":2827},[],{"title":10,"searchDepth":11,"depth":11,"links":2828},[],{},"Victor Borg","\u002Fauthors\u002Fvictor-borg",{"title":2546,"description":10},"authors\u002Fvictor-borg","-Za-JweoiP6hyclue_WkxMXdRUDTczPGlJf6AZckjUc",{"id":2836,"title":2546,"body":2837,"description":10,"extension":13,"meta":2841,"name":2842,"navigation":16,"path":2843,"readingTime":18,"seo":2844,"stem":2845,"__hash__":2846},"authors\u002Fauthors\u002Fvirgil-roger.md",{"type":7,"value":2838,"toc":2839},[],{"title":10,"searchDepth":11,"depth":11,"links":2840},[],{},"Virgil Roger","\u002Fauthors\u002Fvirgil-roger",{"title":2546,"description":10},"authors\u002Fvirgil-roger","DfVFe5j0bCgXeEr381ZYOM5DP4m-pWb93J9-m_muKJ0",{"id":2848,"title":2546,"body":2849,"description":10,"extension":13,"meta":2853,"name":2854,"navigation":16,"path":2855,"readingTime":18,"seo":2856,"stem":2857,"__hash__":2858},"authors\u002Fauthors\u002Fyukan-zhao.md",{"type":7,"value":2850,"toc":2851},[],{"title":10,"searchDepth":11,"depth":11,"links":2852},[],{},"Yukan Zhao","\u002Fauthors\u002Fyukan-zhao",{"title":2546,"description":10},"authors\u002Fyukan-zhao","LRPHugtAJnWHsmHxy9_SR5Zas_C5p-GR_uHEs1Fhk_E",1778159248267]