CINXE.COM
<!DOCTYPE html><html lang="it" data-theme-enabled="1"><head><script>window.currentUser = null;</script><script>window.shopCurrency = "EUR";</script><script>window.localCurrency = "EUR";</script><script>window.countryCode = "lt";</script><script>window.rateShopTo = {"EUR":1,"USD":1.0784426856242513,"AMD":422.9305849580972};</script><title itemprop="name">Gestione degli errori con le promise</title><link href="/pack/styles.100020a0bc7cf13be729.css" rel="stylesheet"><meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=yes, minimum-scale=1.0"><meta name="apple-mobile-web-app-capable" content="yes"><!-- chrome autotranslate is enabled only for "en" main version--><meta name="google" content="notranslate"><script>if (window.devicePixelRatio > 1) document.cookie = 'pixelRatio=' + window.devicePixelRatio + ';path=/;expires=Tue, 19 Jan 2038 03:14:07 GMT';</script><link href="//fonts.googleapis.com/css?family=Open+Sans:bold,italic,bolditalic" rel="stylesheet"><link rel="apple-touch-icon-precomposed" href="/img/favicon/apple-touch-icon-precomposed.png"><link rel="canonical" href="https://it.javascript.info/promise-error-handling"><meta name="msapplication-TileColor" content="#222A2C"><meta name="msapplication-TileImage" content="/img/favicon/tileicon.png"><link rel="icon" href="/img/favicon/favicon.png"><meta itemprop="image" content="https://it.javascript.info/img/site_preview_en_512x512.png"><meta property="og:title" content="Gestione degli errori con le promise"><meta property="og:image" content="https://it.javascript.info/img/site_preview_en_1200x630.png"><meta property="og:image:type" content="image/png"><meta property="og:image:width" content="1200"><meta property="og:image:height" content="630"><meta property="fb:admins" content="100001562528165"><meta name="twitter:card" content="summary"><meta name="twitter:title" content="Gestione degli errori con le promise"><meta name="twitter:site" content="@iliakan"><meta name="twitter:creator" content="@iliakan"><meta name="twitter:image" content="https://it.javascript.info/img/site_preview_en_512x512.png"><meta name="google-adsense-account" content="ca-pub-6204518652652613"><link rel="prev" href="/promise-chaining"><link rel="next" href="/promise-api"><script data-collect-dnt="true" async src="https://scripts.simpleanalyticscdn.com/latest.js"></script><script>window.GA_ID = "UA-2056213-15";</script><script>window.YANDEX_METRIKA_ID = 32184394;</script><script>{function gtag(){dataLayer.push(arguments)}window.dataLayer=window.dataLayer||[],gtag("js",new Date),gtag("config","G-2LWB61WGYJ")}</script> <script async src="https://www.googletagmanager.com/gtag/js?id=G-2LWB61WGYJ"></script><script>window.metrika={reachGoal:function(){}},window.yandex_metrika_callbacks=[function(){try{window.metrika=new Ya.Metrika({id:YANDEX_METRIKA_ID,webvisor:!0,clickmap:!0,params:{user:window.currentUser&&window.currentUser.id}}),metrika.trackLinks({delay:150}),window.addEventListener("error",function(r){window.metrika.reachGoal("JSERROR",{src:(r.filename||r.errorUrl)+": "+(r.lineno||r.errorLine),stack:r.stack||r.error&&r.error.stack,message:r.message})})}catch(r){}}];</script><script src="//mc.yandex.ru/metrika/watch.js" async></script><script>window.RECAPTCHA_ID = "6LfmLAEVAAAAAJMykMnf7aY8nkyTRmYi2ynx51R1";</script><script src="/pack/init.e9944d85765973026934.js"></script><script src="/pack/head.0bda608a64e89b2bf1e8.js" defer></script><script> class HttpError extends Error { constructor(response) { super(`${response.status} for ${response.url}`); this.name = 'HttpError'; this.response = response; } } function loadJson(url) { return fetch(url) .then(response => { if (response.status == 200) { return response.json(); } else { throw new HttpError(response); } }) } </script> <style> .promise-avatar-example { border-radius: 50%; position: fixed; left: 10px; top: 10px; } </style> <meta property="og:title" content="Gestione degli errori con le promise"><meta property="og:type" content="article"><script src="/pack/tutorial.68709f8acff7a5842a1b.js" defer></script><script src="/pack/footer.ef1dc588044c3d1cdc9f.js" defer></script></head><body class="no-icons"><script>window.fontTest();</script><div class="page-wrapper page-wrapper_sidebar_on"><!--[if IE]><div style="color:red;text-align:center">Purtroppo Internet Explorer non è supportato, ti preghiamo di utilizzare un browser più recente.</div><![endif]--><div class="sitetoolbar sitetoolbar_tutorial"><script>window.langs = [{"code":"ar","name":"Arabic"},{"code":"az","name":"Azerbaijani"},{"code":"bg","name":"Bulgarian"},{"code":"bn","name":"Bengali"},{"code":"bs","name":"Bosnian"},{"code":"ca","name":"Catalan"},{"code":"cs","name":"Czech"},{"code":"da","name":"Danish"},{"code":"de","name":"German"},{"code":"el","name":"Greek"},{"code":"en","name":"English"},{"code":"es","name":"Spanish"},{"code":"fa","name":"Persian (Farsi)"},{"code":"fi","name":"Finnish"},{"code":"fr","name":"French"},{"code":"he","name":"Hebrew"},{"code":"hi","name":"Hindi"},{"code":"hr","name":"Croatian"},{"code":"hu","name":"Hungarian"},{"code":"hy","name":"Armenian"},{"code":"id","name":"Indonesian"},{"code":"it","name":"Italian"},{"code":"ja","name":"Japanese"},{"code":"ka","name":"Georgian"},{"code":"kk","name":"Kazakh"},{"code":"km","name":"Central Khmer"},{"code":"ko","name":"Korean"},{"code":"ku","name":"Kurdish"},{"code":"ky","name":"Kyrgyz"},{"code":"lt","name":"Lithuanian"},{"code":"me","name":"Montenegrin"},{"code":"ml","name":"Malayalam"},{"code":"ms","name":"Malay"},{"code":"my","name":"Burmese"},{"code":"nl","name":"Dutch"},{"code":"no","name":"Norvegian"},{"code":"pa","name":"Punjabi"},{"code":"pl","name":"Polish"},{"code":"pt","name":"Portuguese"},{"code":"ro","name":"Romanian"},{"code":"ru","name":"Russian"},{"code":"si","name":"Sinhala"},{"code":"sk","name":"Slovak"},{"code":"sl","name":"Slovenian"},{"code":"sq","name":"Albanian"},{"code":"sr","name":"Serbian"},{"code":"ta","name":"Tamil"},{"code":"te","name":"Telugu"},{"code":"test","name":"Test"},{"code":"th","name":"Thai"},{"code":"tk","name":"Turkmen"},{"code":"tr","name":"Turkish"},{"code":"ug","name":"Uyghur"},{"code":"uk","name":"Ukrainian"},{"code":"ur","name":"Urdu"},{"code":"uz","name":"Uzbek"},{"code":"v2","name":"v2"},{"code":"vi","name":"Vietnamese"},{"code":"zh-hant","name":"Chinese Traditional"},{"code":"zh","name":"Chinese"}];</script><script>window.lang = "it";</script><div class="sitetoolbar__content"><div class="sitetoolbar__lang-switcher"><button class="sitetoolbar__dropdown-button" data-dropdown-toggler>IT</button><div class="sitetoolbar__dropdown-wrap"><div class="sitetoolbar__dropdown-body"><div class="sitetoolbar__lang-switcher-body"><div class="supported-langs supported-langs_toolbar"><div class="supported-langs__container"><ul class="supported-langs__list" style="height:200px"><li class="supported-langs__item"><a class="supported-langs__link" href="https://ar.javascript.info/promise-error-handling"><span class="supported-langs__brief">AR</span><span class="supported-langs__title">عربي</span></a></li><li class="supported-langs__item"><a class="supported-langs__link" href="https://javascript.info/promise-error-handling"><span class="supported-langs__brief">EN</span><span class="supported-langs__title">English</span></a></li><li class="supported-langs__item"><a class="supported-langs__link" href="https://es.javascript.info/promise-error-handling"><span class="supported-langs__brief">ES</span><span class="supported-langs__title">Español</span></a></li><li class="supported-langs__item"><a class="supported-langs__link" href="https://fa.javascript.info/promise-error-handling"><span class="supported-langs__brief">FA</span><span class="supported-langs__title">فارسی</span></a></li><li class="supported-langs__item"><a class="supported-langs__link" href="https://fr.javascript.info/promise-error-handling"><span class="supported-langs__brief">FR</span><span class="supported-langs__title">Français</span></a></li><li class="supported-langs__item"><a class="supported-langs__link" href="https://id.javascript.info/promise-error-handling"><span class="supported-langs__brief">ID</span><span class="supported-langs__title">Indonesia</span></a></li></ul><ul class="supported-langs__list" style="height:200px"><li class="supported-langs__item supported-langs__item_current"><a class="supported-langs__link" href="https://it.javascript.info/promise-error-handling"><span class="supported-langs__brief">IT</span><span class="supported-langs__title">Italiano</span></a></li><li class="supported-langs__item"><a class="supported-langs__link" href="https://ja.javascript.info/promise-error-handling"><span class="supported-langs__brief">JA</span><span class="supported-langs__title">日本語</span></a></li><li class="supported-langs__item"><a class="supported-langs__link" href="https://ko.javascript.info/promise-error-handling"><span class="supported-langs__brief">KO</span><span class="supported-langs__title">한국어</span></a></li><li class="supported-langs__item"><a class="supported-langs__link" href="https://learn.javascript.ru/promise-error-handling"><span class="supported-langs__brief">RU</span><span class="supported-langs__title">Русский</span></a></li><li class="supported-langs__item"><a class="supported-langs__link" href="https://tr.javascript.info/promise-error-handling"><span class="supported-langs__brief">TR</span><span class="supported-langs__title">Türkçe</span></a></li><li class="supported-langs__item"><a class="supported-langs__link" href="https://uk.javascript.info/promise-error-handling"><span class="supported-langs__brief">UK</span><span class="supported-langs__title">Українська</span></a></li></ul><ul class="supported-langs__list" style="height:20px"><li class="supported-langs__item"><a class="supported-langs__link" href="https://zh.javascript.info/promise-error-handling"><span class="supported-langs__brief">ZH</span><span class="supported-langs__title">简体中文</span></a></li></ul></div><div class="supported-langs__text"><p>Vorremo rendere disponibile questo progetto open-source per persone in tutto il mondo.</p> <p><a href="https://javascript.info/translate">Aiutaci a tradurre</a> il contenuto di questo tutorial nella tua lingua!</p> </div></div></div></div></div></div><div class="sitetoolbar__logo-wrap"><a class="sitetoolbar__link sitetoolbar__link_logo" href="/"><img class="sitetoolbar__logo sitetoolbar__logo_normal" src="/img/sitetoolbar__logo_en.svg" width="200" alt="" role="presentation"/><img class="sitetoolbar__logo sitetoolbar__logo_normal sitetoolbar__logo_dark" src="/img/sitetoolbar__logo_en-white.svg" width="200" alt="" role="presentation"/><img class="sitetoolbar__logo sitetoolbar__logo_small" src="/img/sitetoolbar__logo_small_en.svg" width="70" alt="" role="presentation"/><img class="sitetoolbar__logo sitetoolbar__logo_small sitetoolbar__logo_dark" src="/img/sitetoolbar__logo_small_en-white.svg" width="70" alt="" role="presentation"/><script>Array.prototype.forEach.call(document.querySelectorAll("img.sitetoolbar__logo"),function(e){let t=document.createElement("object");t.type="image/svg+xml",t.className=e.className,t.style.cssText="left:0;top:0;position:absolute",t.onload=function(){t.onload=null,e.style.visibility="hidden"},t.data=e.src,e.parentNode.insertBefore(t,e)});</script></a></div><div class="sitetoolbar__nav-toggle-wrap"><button class="sitetoolbar__nav-toggle" type="button"></button></div><nav class="sitetoolbar__sections"><ul class="sitetoolbar__sections-list"></ul></nav><div class="sitetoolbar__right-button-wrap"><a class="sitetoolbar-right-button sitetoolbar-right-button_courses" href="/ebook"><span class="sitetoolbar-right-button__extra-text">Compra</span>EPUB/PDF</a></div><div class="sitetoolbar__theme-switcher"><div class="theme-changer"><label class="theme-changer__label" for="theme-changer-input" data-tooltip="Change theme"><input class="theme-changer__input" type="checkbox" id="theme-changer-input" data-theme-changer="data-theme-changer"/><span class="theme-changer__icon theme-changer__icon_light-theme"></span><span class="theme-changer__icon theme-changer__icon_dark-theme"></span></label></div></div><div class="sitetoolbar__search-wrap"><div class="sitetoolbar__search-content"><button class="sitetoolbar__search-toggle" type="button"></button><form class="sitetoolbar__search" method="GET" action="/search"><div class="sitetoolbar__search-input"><div class="text-input"><input class="text-input__control" name="query" placeholder="Cerca in Javascript.info" required="required" type="text"/></div><button class="sitetoolbar__find" type="submit">Cerca</button></div></form></div></div></div><div class="tablet-menu"><div class="tablet-menu__line"><div class="tablet-menu__content"><form class="tablet-menu-search" action="/search/"><input class="tablet-menu-search__input" type="search" name="query" placeholder="Cerca nel tutorial" required="required"/><button class="tablet-menu-search__button" type="submit" name="type" value="articles">Cerca</button></form></div></div><div class="tablet-menu__line"><div class="tablet-menu__content"><a class="map" href="/tutorial/map" data-action="tutorial-map"><span class="map__text">Mappa del tutorial</span></a></div></div><div class="tablet-menu__line"><div class="tablet-menu__content"><div class="theme-changer theme-changer_tablet-menu theme-changer_has-label"><label class="theme-changer__label" for="theme-changer-input-tablet" data-tooltip="Change theme"><input class="theme-changer__input" type="checkbox" id="theme-changer-input-tablet" data-theme-changer="data-theme-changer"/><span class="theme-changer__icon theme-changer__icon_light-theme"></span><span class="theme-changer__icon theme-changer__icon_dark-theme"></span><span class="theme-changer__label-text theme-changer__label-text_light-theme">Light theme</span><span class="theme-changer__label-text theme-changer__label-text_dark-theme">Dark theme</span></label></div></div></div><div class="tablet-menu__line"><div class="tablet-menu__content"><div class="share-icons"><span class="share-icons__title">Condividi</span><a class="share share_tw" href="https://twitter.com/share?url=https%3A%2F%2Fit.javascript.info%2Fpromise-error-handling" rel="nofollow"></a><a class="share share_fb" href="https://www.facebook.com/sharer/sharer.php?s=100&p%5Burl%5D=https%3A%2F%2Fit.javascript.info%2Fpromise-error-handling" rel="nofollow"></a></div></div></div><div class="tablet-menu__line"><div class="tablet-menu__content"><select class="tablet-menu__nav input-select input-select input-select_small" onchange="if(this.value) window.location.href=this.value"><option value="https://ar.javascript.info/promise-error-handling">عربي</option><option value="https://javascript.info/promise-error-handling">English</option><option value="https://es.javascript.info/promise-error-handling">Español</option><option value="https://fa.javascript.info/promise-error-handling">فارسی</option><option value="https://fr.javascript.info/promise-error-handling">Français</option><option value="https://id.javascript.info/promise-error-handling">Indonesia</option><option value="https://it.javascript.info/promise-error-handling" selected>Italiano</option><option value="https://ja.javascript.info/promise-error-handling">日本語</option><option value="https://ko.javascript.info/promise-error-handling">한국어</option><option value="https://learn.javascript.ru/promise-error-handling">Русский</option><option value="https://tr.javascript.info/promise-error-handling">Türkçe</option><option value="https://uk.javascript.info/promise-error-handling">Українська</option><option value="https://zh.javascript.info/promise-error-handling">简体中文</option></select></div></div></div><progress class="tutorial-progress" data-sticky value="78" max="92" data-tooltip="Lezione 78 di 92"></progress></div><div class="page page_sidebar_on page_inner_padding"><script>if(localStorage.noSidebar){document.querySelector(".page").classList.remove("page_sidebar_on");let e=document.querySelector(".page-wrapper");e&&e.classList.remove("page-wrapper_sidebar_on")}setTimeout(function(){document.querySelector(".page").classList.add("page_sidebar-animation-on")});</script><div class="page__inner"><main class="main main_width-limit"><header class="main__header"><div class="main__header-inner"><div class="main__header-group"><ol class="breadcrumbs"><li class="breadcrumbs__item breadcrumbs__item_home"><a class="breadcrumbs__link" href="/"><span class="breadcrumbs__hidden-text">Tutorial</span></a></li><li class="breadcrumbs__item" id="breadcrumb-1"><a class="breadcrumbs__link" href="/js"><span>Il linguaggio JavaScript</span></a></li><li class="breadcrumbs__item" id="breadcrumb-2"><a class="breadcrumbs__link" href="/async"><span>Promises, async/await</span></a></li><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Tutorial","item":"https://it.javascript.info/"},{"@type":"ListItem","position":2,"name":"Il linguaggio JavaScript","item":"https://it.javascript.info/js"},{"@type":"ListItem","position":3,"name":"Promises, async/await","item":"https://it.javascript.info/async"}]}</script></ol><div class="updated-at" data-tooltip="Ultimo aggiornamento al 15 dicembre 2021"><div class="updated-at__content">15 dicembre 2021</div></div></div><h1 class="main__header-title">Gestione degli errori con le promise</h1></div></header><div class="content"><article class="formatted" itemscope itemtype="http://schema.org/TechArticle"><meta itemprop="name" content="Gestione degli errori con le promise"><div itemprop="author" itemscope itemtype="http://schema.org/Person"><meta itemprop="email" content="iliakan@gmail.com"><meta itemprop="name" content="Ilya Kantor"></div><div itemprop="articleBody"><p>Le azioni asincrione a volte possono fallire: in caso di errore la promise corrispondente viene respinta (rejected). Per esempio, <code>fetch</code> fallisce se il server remoto non è disponibile. Possiamo usare <code>.catch</code> per gestire gli errori (rejections).</p> <p>IL concatenemanto delle Promise è ottimo sotto questo aspetto. Quando una promise viene rifiutata (rejects), il controllo passa al gestore del rifiuto (rejection handler) più vicino nella catena. Questo è molto conveniente.</p> <p>Per esempio, nel codice sotto l’URL è errato (no such server) e <code>.catch</code> gestisce l’errore:</p> <div id="r10bno4gwt" data-trusted="1" class="code-example" data-highlight="[{"start":0,"end":0}]"> <div class="codebox code-example__codebox"> <div class="toolbar codebox__toolbar"> <div class="toolbar__tool"> <a href="#" title="esegui" data-action="run" class="toolbar__button toolbar__button_run"></a> </div> <div class="toolbar__tool"> <a href="#" title="apri in sandbox" target="_blank" data-action="edit" class="toolbar__button toolbar__button_edit"></a> </div> </div> <div class="codebox__code" data-code="1"> <pre class="line-numbers language-javascript"><code>fetch('https://no-such-server.blabla') // viene respinta (rejects) .then(response => response.json()) .catch(err => alert(err)) // TypeError: failed to fetch (il testo può variare)</code></pre> </div> </div> </div><p>Oppure, forse, è tutto a posto con il server, ma la risposta non è JSON valido:</p> <div id="m77qso1zet" data-trusted="1" class="code-example" data-highlight="[{"start":1,"end":1}]"> <div class="codebox code-example__codebox"> <div class="toolbar codebox__toolbar"> <div class="toolbar__tool"> <a href="#" title="esegui" data-action="run" class="toolbar__button toolbar__button_run"></a> </div> <div class="toolbar__tool"> <a href="#" title="apri in sandbox" target="_blank" data-action="edit" class="toolbar__button toolbar__button_edit"></a> </div> </div> <div class="codebox__code" data-code="1"> <pre class="line-numbers language-javascript"><code>fetch('/') // fetch funziona bene adesso, il server risponde con successo .then(response => response.json()) // viene respinta (rejects): the page is HTML, not a valid json .catch(err => alert(err)) // SyntaxError: Unexpected token < in JSON at position 0</code></pre> </div> </div> </div><p>Il modo più facile di catturare (catch) è di mettere <code>.catch</code> alla fine della catena:</p> <div id="91h5dq2f39" data-trusted="1" class="code-example" data-highlight="[{"start":15,"end":15}]"> <div class="codebox code-example__codebox"> <div class="toolbar codebox__toolbar"> <div class="toolbar__tool"> <a href="#" title="esegui" data-action="run" class="toolbar__button toolbar__button_run"></a> </div> <div class="toolbar__tool"> <a href="#" title="apri in sandbox" target="_blank" data-action="edit" class="toolbar__button toolbar__button_edit"></a> </div> </div> <div class="codebox__code" data-code="1"> <pre class="line-numbers language-javascript"><code>fetch('/article/promise-chaining/user.json') .then(response => response.json()) .then(user => fetch(`https://api.github.com/users/${user.name}`)) .then(response => response.json()) .then(githubUser => new Promise((resolve, reject) => { let img = document.createElement('img'); img.src = githubUser.avatar_url; img.className = "promise-avatar-example"; document.body.append(img); setTimeout(() => { img.remove(); resolve(githubUser); }, 3000); })) .catch(error => alert(error.message));</code></pre> </div> </div> </div><p>Normalmente, <code>.catch</code> non viene eseguito, perchè non ci sono errori. Ma se una qualsiasi delle promise sopra viene respinta (un errore di rete o JSON invalido o qualunque cosa), allora l’errore verrebbe catturato.</p> <h2><a class="main__anchor" name="try-catch-implicito" href="#try-catch-implicito">Try…catch implicito</a></h2><p>Il codice di un esecutore (executor) e dei gestori (handlers) delle promise hanno un “<code>try..catch</code> invisibile”. Se si verifica un errore, viene catturato e gestitito come un rigettamento (rejection).</p> <p>Per esempio, questo codice:</p> <div id="opnmboxfpj" data-trusted="1" class="code-example" data-highlight="[{"start":1,"end":1}]"> <div class="codebox code-example__codebox"> <div class="toolbar codebox__toolbar"> <div class="toolbar__tool"> <a href="#" title="esegui" data-action="run" class="toolbar__button toolbar__button_run"></a> </div> <div class="toolbar__tool"> <a href="#" title="apri in sandbox" target="_blank" data-action="edit" class="toolbar__button toolbar__button_edit"></a> </div> </div> <div class="codebox__code" data-code="1"> <pre class="line-numbers language-javascript"><code>new Promise((resolve, reject) => { throw new Error("Whoops!"); }).catch(alert); // Error: Whoops!</code></pre> </div> </div> </div><p>…Funziona esattamente come questo:</p> <div id="o2y0w43sy2" data-trusted="1" class="code-example" data-highlight="[{"start":1,"end":1}]"> <div class="codebox code-example__codebox"> <div class="toolbar codebox__toolbar"> <div class="toolbar__tool"> <a href="#" title="esegui" data-action="run" class="toolbar__button toolbar__button_run"></a> </div> <div class="toolbar__tool"> <a href="#" title="apri in sandbox" target="_blank" data-action="edit" class="toolbar__button toolbar__button_edit"></a> </div> </div> <div class="codebox__code" data-code="1"> <pre class="line-numbers language-javascript"><code>new Promise((resolve, reject) => { reject(new Error("Whoops!")); }).catch(alert); // Error: Whoops!</code></pre> </div> </div> </div><p>Il “<code>try..catch</code> invisibile” intorno all’esecutore (executor) cattura (catches) automaticamente l’errore e lo tratta come un rigettamento (rejection).</p> <p>Questo accade non solo nell’esecutore (executor), ma anche nei suoi gestori (handlers). Se lanciamo (<code>throw</code>) dentro un gestore (handler)<code>.then</code>, questo significa una promise respinta (rejected), così il controllo salta al gestore (handler) degli errori più vicino.</p> <p>Ecco un esempio:</p> <div id="cvwssb3sf1" data-trusted="1" class="code-example" data-highlight="[{"start":3,"end":3}]"> <div class="codebox code-example__codebox"> <div class="toolbar codebox__toolbar"> <div class="toolbar__tool"> <a href="#" title="esegui" data-action="run" class="toolbar__button toolbar__button_run"></a> </div> <div class="toolbar__tool"> <a href="#" title="apri in sandbox" target="_blank" data-action="edit" class="toolbar__button toolbar__button_edit"></a> </div> </div> <div class="codebox__code" data-code="1"> <pre class="line-numbers language-javascript"><code>new Promise((resolve, reject) => { resolve("ok"); }).then((result) => { throw new Error("Whoops!"); // respinge (rejects) la promise }).catch(alert); // Error: Whoops!</code></pre> </div> </div> </div><p>Questo accade per tutti gli errori, non solo quelli causati dallo statement <code>throw</code>. Per esempio, un errore di programmazione:</p> <div id="lrs75t4jlf" data-trusted="1" class="code-example" data-highlight="[{"start":3,"end":3}]"> <div class="codebox code-example__codebox"> <div class="toolbar codebox__toolbar"> <div class="toolbar__tool"> <a href="#" title="esegui" data-action="run" class="toolbar__button toolbar__button_run"></a> </div> <div class="toolbar__tool"> <a href="#" title="apri in sandbox" target="_blank" data-action="edit" class="toolbar__button toolbar__button_edit"></a> </div> </div> <div class="codebox__code" data-code="1"> <pre class="line-numbers language-javascript"><code>new Promise((resolve, reject) => { resolve("ok"); }).then((result) => { blabla(); // non esiste la funzione }).catch(alert); // ReferenceError: blabla is not defined</code></pre> </div> </div> </div><p>Il <code>.catch</code> finale non solo cattura (catches) i rigettamenti (rejections) espiciti, ma anche gli errori occasionali nei gestori (handlers) .</p> <h2><a class="main__anchor" name="rethrowing" href="#rethrowing">Rethrowing</a></h2><p>Come abbiamo già notato, <code>.catch</code> si comporta come <code>try..catch</code>. Possiamo avere tutti i gestori (handler) <code>.then</code> che vogliamo, e poi usare un solo <code>.catch</code> alla fine per gestire tutti gli errori al loro interno.</p> <p>In un normale <code>try..catch</code> possiamo analizzare l’errore e magari rilanciarlo (rethrow) se non può essere gestito. È possibile fare lo stesso con le promise.</p> <p>Se lanciamo (<code>throw</code>) dentro <code>.catch</code>, allora il controllo va al gestore (handler) più vicino. E se gestiamo l’errore e finiamo normalmente, allora continua al prossimo gestore (handler) <code>.then</code> per i casi di successo.</p> <p>Nell’esempio sotto, <code>.catch</code> gestisce con successo l’errore:</p> <div id="a88aylbp5h" data-trusted="1" class="code-example"> <div class="codebox code-example__codebox"> <div class="toolbar codebox__toolbar"> <div class="toolbar__tool"> <a href="#" title="esegui" data-action="run" class="toolbar__button toolbar__button_run"></a> </div> <div class="toolbar__tool"> <a href="#" title="apri in sandbox" target="_blank" data-action="edit" class="toolbar__button toolbar__button_edit"></a> </div> </div> <div class="codebox__code" data-code="1"> <pre class="line-numbers language-javascript"><code>// esecuzione: catch -> then new Promise((resolve, reject) => { throw new Error("Whoops!"); }).catch(function(error) { alert("L'errore è gestito, continua normalmente"); }).then(() => alert("Il prossimo gestore (handler) per i casi di successo viene eseguito"));</code></pre> </div> </div> </div><p>Qui il blocco <code>.catch</code> finisce normalmente. Così il prossimo gestore (handler) <code>.then</code> viene chiamato.</p> <p>Nell’esempio sotto vediamo l’altra situazione con <code>.catch</code>. Il gestore (handler) <code>(*)</code> cattura (catches) l’errore e non può gestirlo (e.g. sa solo come gestire <code>URIError</code>), quindi lo solleva (throws) di nuovo:</p> <div id="6z2gz02deg" data-trusted="1" class="code-example" data-highlight="[{"start":12,"end":12}]"> <div class="codebox code-example__codebox"> <div class="toolbar codebox__toolbar"> <div class="toolbar__tool"> <a href="#" title="esegui" data-action="run" class="toolbar__button toolbar__button_run"></a> </div> <div class="toolbar__tool"> <a href="#" title="apri in sandbox" target="_blank" data-action="edit" class="toolbar__button toolbar__button_edit"></a> </div> </div> <div class="codebox__code" data-code="1"> <pre class="line-numbers language-javascript"><code>// esecuzione: catch -> catch -> then new Promise((resolve, reject) => { throw new Error("Whoops!"); }).catch(function(error) { // (*) if (error instanceof URIError) { // gestiscilo } else { alert("Non posso gestire l'errore"); throw error; // lanciare questo o un altro errore ci fa saltare al prossimo catch } }).then(function() { /* non viene mai eseguito */ }).catch(error => { // (**) alert(`Si è verificato un errore sconosciuto: ${error}`); // non ritornare nulla => l'esecuzione procede normalmente });</code></pre> </div> </div> </div><p>Poi l’esecuzione passa dal primo <code>.catch</code> <code>(*)</code> al prossimo <code>(**)</code> giù per la catena.</p> <p>Nella sezione sotto vedremo un pratico esempio di risollevamento (rethrowing).</p> <h2><a class="main__anchor" name="gestione-degli-errori-di-fetch" href="#gestione-degli-errori-di-fetch">Gestione degli errori di fetch</a></h2><p>Miglioriamo la gestione degli errori per l’esempio del caricamento degli utenti.</p> <p>La promise ritornata da <a href="https://developer.mozilla.org/it/docs/Web/API/WindowOrWorkerGlobalScope/fetch">fetch</a> viene respinta (rejects) quando è impossibile fare una richiesta. Per esempio, un server remoto non è disponibile, o l’URL è malformato. Ma se il server remoto risponde con un errore 404, o anche un errore 500, allora è considerata una risposta valida.</p> <p>Cosa accade se il server ritorna una pagina non JSON con un errore 500 nella linea <code>(*)</code>? Cosa accade se l’utente non esiste e GitHub ritorna una pagina conun errore 404 a <code>(**)</code>?</p> <div id="ah71zxz17b" data-trusted="1" class="code-example"> <div class="codebox code-example__codebox"> <div class="toolbar codebox__toolbar"> <div class="toolbar__tool"> <a href="#" title="esegui" data-action="run" class="toolbar__button toolbar__button_run"></a> </div> <div class="toolbar__tool"> <a href="#" title="apri in sandbox" target="_blank" data-action="edit" class="toolbar__button toolbar__button_edit"></a> </div> </div> <div class="codebox__code" data-code="1"> <pre class="line-numbers language-javascript"><code>fetch('no-such-user.json') // (*) .then(response => response.json()) .then(user => fetch(`https://api.github.com/users/${user.name}`)) // (**) .then(response => response.json()) .catch(alert); // SyntaxError: Unexpected token < in JSON at position 0 // ...</code></pre> </div> </div> </div><p>Allo stato attuale, il codice prova comunque a caricare la risposta come JSON e muore con un errore di sintassi. Lo puoi vedere eseguendo l’esempio sopra, dato che il file <code>no-such-user.json</code> non esiste.</p> <p>Questo non è buono, perchè l’errore va semplicemente giù nella catena, senza dettagli: cosa è fallito e dove.</p> <p>Quindi aggiungiamo un altro passo: dovremmo controllare la proprietà <code>response.status</code> che ha lo stato HTTP, e se non è 200, allora lanciare un errore.</p> <div id="59tej8gnjl" data-trusted="1" class="code-example"> <div class="codebox code-example__codebox"> <div class="toolbar codebox__toolbar"> <div class="toolbar__tool"> <a href="#" title="esegui" data-action="run" class="toolbar__button toolbar__button_run"></a> </div> <div class="toolbar__tool"> <a href="#" title="apri in sandbox" target="_blank" data-action="edit" class="toolbar__button toolbar__button_edit"></a> </div> </div> <div class="codebox__code" data-code="1"> <pre class="line-numbers language-javascript"><code>class HttpError extends Error { // (1) constructor(response) { super(`${response.status} for ${response.url}`); this.name = 'HttpError'; this.response = response; } } function loadJson(url) { // (2) return fetch(url) .then(response => { if (response.status == 200) { return response.json(); } else { throw new HttpError(response); } }) } loadJson('no-such-user.json') // (3) .catch(alert); // HttpError: 404 for .../no-such-user.json</code></pre> </div> </div> </div><ol> <li>Creiamo una classe custom per gli errori HTTP per distinguerli dagli altri tipi di errore. Inoltre, la nuova classe ha un costruttore che accetta l’oggetto <code>response</code> e lo salva nell’errore. Così il codice per la gestione degli errori sarà in grado di accedervi.</li> <li>Dopo mettiamo insieme il codice per effettuare la richiesta e per gestire gli errori in una funzione che recupera l’<code>url</code> <em>e</em> tratta ogni stato non 200 come un errore. È conveniente, perchè spesso avremo bisogno di una logica simile.</li> <li>Ora <code>alert</code> mostra un messaggio più utile e descrittivo.</li> </ol> <p>Il bello di avere una nostra classe per gli errori è che possiamo facilmente verificarli nel nostro codice di gestione degli errori.</p> <p>Per esempio, possiamo fare una richiesta, e poi se riceviamo un 404 – chiedere all’utente di modificare l’informazione.</p> <p>Il codice sotto carica un utente con il nome da GitHub. Se non c’è l’utente, allora chiede il nome corretto:</p> <div id="390scjttib" data-trusted="1" class="code-example" data-highlight="[{"start":9,"end":9}]"> <div class="codebox code-example__codebox"> <div class="toolbar codebox__toolbar"> <div class="toolbar__tool"> <a href="#" title="esegui" data-action="run" class="toolbar__button toolbar__button_run"></a> </div> <div class="toolbar__tool"> <a href="#" title="apri in sandbox" target="_blank" data-action="edit" class="toolbar__button toolbar__button_edit"></a> </div> </div> <div class="codebox__code" data-code="1"> <pre class="line-numbers language-javascript"><code>function demoGithubUser() { let name = prompt("Inserire un nome", "iliakan"); return loadJson(`https://api.github.com/users/${name}`) .then(user => { alert(`Full name: ${user.name}.`); return user; }) .catch(err => { if (err instanceof HttpError && err.response.status == 404) { alert("Utente inesistente, per favorlo nuovamente."); return demoGithubUser(); } else { throw err; // (*) } }); } demoGithubUser();</code></pre> </div> </div> </div><p>Notare che: <code>.catch</code> cattura tutti gli errori, ma “sa come gestire” solo <code>HttpError 404</code>. In questo caso particolare significa che non esiste l’utente, e <code>.catch</code> in questo caso riprova semplicemente.</p> <p>Per altri errori, non ha idea di cosa possa andare stoto. Magari un errore di programmazione o altro. Quindi semplicemente lo risoleva nella linea <code>(*)</code>.</p> <h2><a class="main__anchor" name="rigetti-non-gestiti-unhandled-rejections" href="#rigetti-non-gestiti-unhandled-rejections">Rigetti non gestiti (unhandled rejections)</a></h2><p>Cosa accade se un errore non è gestito? Per esempio, dopo il risollevamento <code>(*)</code> nell’esempio sopra.</p> <p>Oppure possiamo semplicemente dimenticarci di aggiungere un gestore (handler) dell’errore alla fine della catena, come qui:</p> <div id="rmebqclhoy" data-trusted="0" class="code-example" data-refresh="1"> <div class="codebox code-example__codebox"> <div class="toolbar codebox__toolbar"> <div class="toolbar__tool"> <a href="#" title="esegui" data-action="run" class="toolbar__button toolbar__button_run"></a> </div> <div class="toolbar__tool"> <a href="#" title="apri in sandbox" target="_blank" data-action="edit" class="toolbar__button toolbar__button_edit"></a> </div> </div> <div class="codebox__code" data-code="1"> <pre class="line-numbers language-javascript"><code>new Promise(function() { noSuchFunction(); // Errore qui (non esiste la funzione) }) .then(() => { // zero o molti handler di promise }); // senza .catch alla fine!</code></pre> </div> </div> </div><p>Nel caso di un errore, lo stato della promise diventa “rejected”, e l’esecuzione dovrebbe saltare al gestore del respingimento (rejection handler). Ma negli esempi sopra non c’è questo gestore (handler). Quindi l’errore porta ad un “blocco”.</p> <p>In pratica, proprio come con un normale errore non gestito, significa che qualcosa è andato terribilmente storto.</p> <p>Cosa accade quando viene sollevato un errore e non viene gestito da <code>try..catch</code>? Lo script muore. Lo stesso accade con una promise rigettata che non viene gestita.</p> <p>La maggior parte dei motori JavaScript tracciano queste situazioni e generano un errore globale in questo caso. Possiamo vederlo nella console.</p> <p>Nel browser possiamo catturare (catch) questi errori usando <code>unhandledrejection</code>:</p> <div id="fakkqhn0je" data-trusted="1" class="code-example" data-highlight="[{"start":0,"end":4}]"> <div class="codebox code-example__codebox"> <div class="toolbar codebox__toolbar"> <div class="toolbar__tool"> <a href="#" title="esegui" data-action="run" class="toolbar__button toolbar__button_run"></a> </div> <div class="toolbar__tool"> <a href="#" title="apri in sandbox" target="_blank" data-action="edit" class="toolbar__button toolbar__button_edit"></a> </div> </div> <div class="codebox__code" data-code="1"> <pre class="line-numbers language-javascript"><code>window.addEventListener('unhandledrejection', function(event) { // L'oggetto evento ha due proprietà speciali: alert(event.promise); // [object Promise] - la promise che ha causato l'errore alert(event.reason); // Error: Whoops! - L'oggetto errore non gestito }); new Promise(function() { throw new Error("Whoops!"); }); // nessun catch per gestire l'errore</code></pre> </div> </div> </div><p>L’evento è parte dello <a href="https://html.spec.whatwg.org/multipage/webappapis.html#unhandled-promise-rejections">standard HTML</a>.</p> <p>Se un errore si verifica, e non c’è nessun <code>.catch</code>, il gestore (handler) <code>unhandledrejection</code> viene lanciato, e riceve l’oggetto <code>event</code> con le informazioni riguardanti l’errore, in questo modo possiamo fare qualcosa.</p> <p>Solitamente questi errori sono irrecuperabili, quindi la cosa migliore da fare è informare l’utente del problema e probabilmente riportare l’incidente al server.</p> <p>In ambienti esterni al browser come Node.js ci sono altri modi simili di tracciare gli errori non gestiti.</p> <h2><a class="main__anchor" name="riepilogo" href="#riepilogo">Riepilogo</a></h2><ul> <li><code>.catch</code> gestisce i respingimenti (rejections) delle promise di tutti i tipi: che sia una chiamata <code>reject()</code>, o un errore sollevato in un gestore (handler).</li> <li>Dovremmo mettere <code>.catch</code> esattamente nei posti in cui vogliamo gestire gli errori sapendo come gestirli. Il gestore (handler) dovrebbe analizzare gli errori (Le classi di errori ci sono di aiuto) e ri-sollevare (rethrow) quelli sconosciuti.</li> <li>È normale non usare <code>.catch</code> se non sappiamo come gestire gli errori (tutti gli errori sono irrecuperabili).</li> <li>In ogni caso dovremmo avere i gestore (handler) dell’evento <code>unhandledrejection</code> (per il browser e quelli analoghi per gli altri ambienti), per tracciare gli errori non gestiti ed informarne l’utente (e probabilmente il nostro server), così che non accada mai che la nostra app “muoia e basta”.</li> </ul> <p>Ed infine, se abbiamo l’indicatore di caricamento, allora <code>.finally</code> è un ottimo gestore (handler) per fermarlo quando il caricamento è completo:</p> <div id="cm73i1l87w" data-trusted="1" class="code-example" data-highlight="[{"start":6,"end":9},{"start":3,"end":3}]"> <div class="codebox code-example__codebox"> <div class="toolbar codebox__toolbar"> <div class="toolbar__tool"> <a href="#" title="esegui" data-action="run" class="toolbar__button toolbar__button_run"></a> </div> <div class="toolbar__tool"> <a href="#" title="apri in sandbox" target="_blank" data-action="edit" class="toolbar__button toolbar__button_edit"></a> </div> </div> <div class="codebox__code" data-code="1"> <pre class="line-numbers language-javascript"><code>function demoGithubUser() { let name = prompt("Enter a name?", "iliakan"); document.body.style.opacity = 0.3; // (1) avvia l'indicatore return loadJson(`https://api.github.com/users/${name}`) .finally(() => { // (2) stop the indication document.body.style.opacity = ''; return new Promise(resolve => setTimeout(resolve)); // (*) }) .then(user => { alert(`Full name: ${user.name}.`); return user; }) .catch(err => { if (err instanceof HttpError && err.response.status == 404) { alert("Utente inesistente, per favorlo nuovamente."); return demoGithubUser(); } else { throw err; } }); } demoGithubUser();</code></pre> </div> </div> </div><p>Qui nella linea <code>(1)</code> indichiamo il caricamento oscurando il documento. Il metodo non conta, avremmo potuto usare qualunque altro tipo di indicazione.</p> <p>Quando la promise è ferma (settled), che sia un fetch con successo o un errore, <code>finally</code> viene lanciato nella linea <code>(2)</code> ferma l’indicatore.</p> <p>C’è un piccolo trucco per i browser <code>(*)</code> nel ritornare una promise con timeout zero da <code>finally</code>. Questo perché alcuni browser (come Chrome) hanno bisogno “di un po’ di tempo” fuori dai gestori (handlers) per diegnare cambiamenti al documento. Questo assicura che l’indicazione è visivamente ferma prima di andare avanti nella catena.</p> </div></article><div class="tasks formatted"><h2 class="tasks__title" id="tasks"><a class="tasks__title-anchor main__anchor main__anchor main__anchor_noicon" href="#tasks">Esercizi</a></h2><div class="task tasks__task"><div class="task__header"><div class="task__title-wrap"><h3 class="task__title"><a class="main__anchor" href="#error-in-settimeout" name="error-in-settimeout">Error in setTimeout</a></h3><a class="task__open-link" href="/task/error-async" target="_blank"></a></div><div class="task__header-note"></div><div class="task__content"><div class="task__formatted"><p>Che cosa pensi? Il <code>.catch</code> sarà eseguito? Spiega la tua risposta.</p> <div id="xyisqy446g" data-trusted="1" class="code-example"> <div class="codebox code-example__codebox"> <div class="codebox__code" data-code="1"> <pre class="line-numbers language-javascript"><code>new Promise(function(resolve, reject) { setTimeout(() => { throw new Error("Whoops!"); }, 1000); }).catch(alert);</code></pre> </div> </div> </div></div><button class="task__solution" type="button">soluzione</button><div class="task__answer"><div class="task__answer-content"><div class="formatted"><p>La risposta è: <strong>no, non sarà eseguito</strong>:</p> <div id="lwphbe3vmw" data-trusted="1" class="code-example"> <div class="codebox code-example__codebox"> <div class="toolbar codebox__toolbar"> <div class="toolbar__tool"> <a href="#" title="esegui" data-action="run" class="toolbar__button toolbar__button_run"></a> </div> <div class="toolbar__tool"> <a href="#" title="apri in sandbox" target="_blank" data-action="edit" class="toolbar__button toolbar__button_edit"></a> </div> </div> <div class="codebox__code" data-code="1"> <pre class="line-numbers language-javascript"><code>new Promise(function(resolve, reject) { setTimeout(() => { throw new Error("Whoops!"); }, 1000); }).catch(alert);</code></pre> </div> </div> </div><p>Come detto nel capitolo, c’è un"<code>try..catch</code> implicito" attorno al codice della funzione. In questo modo tutti gli errori sincroni sono gestiti.</p> <p>Tuttavia qui l’errore è generato non mentre sta venendo eseguito l’esecutore, ma dopo. Per questo motivo la promise non può gestirlo.</p> </div></div><button class="close-button task__answer-close" type="button" title="chiudi"></button></div></div></div></div></div></div><div class="page__nav-wrap"><a class="page__nav page__nav_prev" href="/promise-chaining" data-tooltip="Concatenamento di promise (promise chaining)"><span class="page__nav-text"><span class="page__nav-text-shortcut"></span></span><span class="page__nav-text-alternate">Lezione precedente</span></a><a class="page__nav page__nav_next" href="/promise-api" data-tooltip="Promise API"><span class="page__nav-text"><span class="page__nav-text-shortcut"></span></span><span class="page__nav-text-alternate">Lezione successiva</span></a></div><div class="article-tablet-foot tablet-only"><div class="article-tablet-foot__layout"><div class="share-icons"><span class="share-icons__title">Condividi</span><a class="share share_tw" href="https://twitter.com/share?url=https%3A%2F%2Fit.javascript.info%2Fpromise-error-handling" rel="nofollow"></a><a class="share share_fb" href="https://www.facebook.com/sharer/sharer.php?s=100&p%5Burl%5D=https%3A%2F%2Fit.javascript.info%2Fpromise-error-handling" rel="nofollow"></a></div><div class="article-tablet-foot__map"><a class="map" href="/tutorial/map" data-action="tutorial-map"><span class="map__text">Mappa del tutorial</span></a></div></div></div><div class="comments formatted" id="comments"><div class="comments__header"><h2 class="comments__header-title"><a href="#comments" name="comments">Commenti</a></h2><div class="comments__read-before"><span class="comments__read-before-link">leggi questo prima di lasciare un commento…</span><div class="comments__read-before-popup"><div class="comments__read-before-popup-i"><ul><li>Per qualsiasi suggerimento - per favore, <a href="https://github.com/javascript-tutorial/en.javascript.info/issues/new">apri una issue su GitHub</a> o una pull request, piuttosto di lasciare un commento.</li><li>Se non riesci a comprendere quanto scitto nell'articolo – ti preghiamo di fornire una spiegazione chiara.</li><li>Per inserire delle righe di codice utilizza il tag <code><code></code>, per molte righe – includile nel tag <code><pre></code>, per più di 10 righe – utilizza una sandbox (<a href='https://plnkr.co/edit/?p=preview'>plnkr</a>, <a href='https://jsbin.com'>jsbin</a>, <a href='http://codepen.io'>codepen</a>…)</li></ul></div></div></div></div><div id="disqus_thread"></div><script>var disqus_config = function() { if (!this.page) this.page = {}; Object.assign(this.page, {"url":"https:\/\/it.javascript.info\/promise-error-handling","identifier":"\/promise-error-handling"}); };</script><script>var disqus_shortname = "it-javascript-info";</script><script>var disqus_enabled = true;</script></div></script></main></div><div class="sidebar page__sidebar sidebar sidebar_sticky-footer"><button class="sidebar__toggle" data-sidebar-toggle></button><a class="map" href="/tutorial/map" data-action="tutorial-map" data-tooltip="Mappa del tutorial"></a><div class="sidebar__inner"><div class="sidebar__content"><div class="sidebar__section"><h4 class="sidebar__section-title">Capitolo</h4><nav class="sidebar__navigation"><ul class="sidebar__navigation-links"><li class="sidebar__navigation-link"><a class="sidebar__link" href="/async">Promises, async/await</a></li></ul></nav></div><div class="sidebar__section"><h4 class="sidebar__section-title">Naviga tra le lezioni</h4><nav class="sidebar__navigation"><ul class="sidebar__navigation-links"><li class="sidebar__navigation-link"><a class="sidebar__link" href="#try-catch-implicito">Try…catch implicito</a></li><li class="sidebar__navigation-link"><a class="sidebar__link" href="#rethrowing">Rethrowing</a></li><li class="sidebar__navigation-link"><a class="sidebar__link" href="#gestione-degli-errori-di-fetch">Gestione degli errori di fetch</a></li><li class="sidebar__navigation-link"><a class="sidebar__link" href="#rigetti-non-gestiti-unhandled-rejections">Rigetti non gestiti (unhandled rejections)</a></li><li class="sidebar__navigation-link"><a class="sidebar__link" href="#riepilogo">Riepilogo</a></li></ul></nav></div><div class="sidebar__section"><nav class="sidebar__navigation"><ul class="sidebar__navigation-links"><li class="sidebar__navigation-link"><a class="sidebar__link" href="#tasks">Esercizi (1)</a></li><li class="sidebar__navigation-link"><a class="sidebar__link" href="#comments">Commenti</a></li></ul></nav></div><div class="sidebar__section"><div class="sidebar__section-title">Condividi</div><a class="share share_tw sidebar__share" href="https://twitter.com/share?url=https%3A%2F%2Fit.javascript.info%2Fpromise-error-handling" rel="nofollow"></a><a class="share share_fb sidebar__share" href="https://www.facebook.com/sharer/sharer.php?s=100&p[url]=https%3A%2F%2Fit.javascript.info%2Fpromise-error-handling" rel="nofollow"></a></div><div class="sidebar__section"><a class="sidebar__link" href="https://github.com/javascript-tutorial/it.javascript.info/blob/master/1-js/11-async/04-promise-error-handling" rel="nofollow">Modifica su GitHub</a></div></div></div></div></div></div><div class="page-footer"><ul class="page-footer__list"><li class="page-footer__item page-footer__item_copy">© 2007—2025 Ilya Kantor</li><li class="page-footer__item page-footer__item_about"><a class="page-footer__link" href="/about">riguardo il progetto</a></li><li class="page-footer__item page-footer__item_contact"><a class="page-footer__link" href="/about#contact-us">contattaci</a></li></ul></div></body></html>