Не смог ответить на вопрос интервьювера: catch vs onRejected

Только что было интервью по скайпу. Первый раз проходил его не в офисе. Вроде не нервничал, но прошло все ужасно. Хуже еще не было. Хотел поинтересоваться, кто смог бы ответить на этот вопрос(устно) и как. Вопрос развернуто: «В чем разница между обработкой ошибки в кинутой в промисе с помощью второго аргумента к then(или onRejected handler) и catch». Я ясное дело глянул в гугле уже и думаю что лучше было бы это было скинуть пример кода конкретного. Так было бы понятней. Но это опровдания... Просто не успокоюсь, пока не поделюсь с кем то. Еще был вопрос — "Что может быть причиной unhendledRejection если если есть правильно зарегистрированный errorHandler с помощью catch"(не дословно). Мне этот вопрос показался странным. Но наверно я просто не правильно понял его.

👍ПодобаєтьсяСподобалось0
До обраногоВ обраному0
LinkedIn
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

Это вопрос с подвохом.

Правильный ответ: В стандарте Promise/A+ отсутствует catch, поэтому его поведение зависит от того кто и как делает реализацию. 99% интервьюверов после этого ответа сливаются и не могут задать уточняющий вопрос.

Просто не успокоюсь, пока не поделюсь с кем то

 Чем именно ты хотел поделиться? Тем что на конкретном проекте, интересуются малозначимой с твоей точки зрения тонкостью работы промисов?

С моей точки зрения, на проекте, где код написан на промисах, а не на async/await это не тонкость, а базовое знание. Без его понимания, ты не сможешь отлавливать в нужном месте цепочки промисов ошибки. Ниже писали об этой проблеме промисов — их паршивой читаемости. Поэтому, скорее всего вопрос был абсолютно обоснован.

А может ты хотел поделить тем, что все

Все прошло ужасно

? Ведь это ужасно, что тебе не надо было ехать в офис, что на тебя потратили время, что ты получил первый опыт прохождения удаленного интервью, что теперь ты знаешь рынок труда немного лучше.

Или может ты хотел поделиться темой, на которую прореагируют и будет о чем писать на интересном.айти? Это мысль ко мне всегда приходит, когда появляется очередной аноним.

Именно тем что прошло ужасно. Несмотря на все обективные плюсы, как получение опыта, я все равно был расторен. Эмоции не имеют ничего общего с рац. мышлением. И увидев первый же коммент с поддержкой мне стало намного легче. Чесно.
П.С. Не то что бы я после интервью рыдал, но просто не по себе было.

То что это базовое знание я нигде не спорил и написал, что это все вообще оправдания в конце поста.

Что такое интересном.айти я не знаю.

базовое — это что из .then можно вернуть новый промис.
озвученная фишка, если раньше о таком не задумывался, запоминается сразу, как только с ней встретишься. например, как укороченное выполнение логических выражений.

С одной стороны ответ гуглится на раз при необходимости, но это ведь базовые понятия, которые даже ждун должен знать достаточно глубоко, если JS — это основной язык.

Не назвал бы это базовыми понятиями для джуна.

Я когда искал работу джуном C# хорошо понимал что такое Task и внутрятку .NET в целом. Как без этого можно писать нормальный код?

Ты не сравнивай джнуа C# и джуна JS.
Джун C# (как и джавист) это минимум 1,5 года комерческого опыта. И базовое понимания платформы, стандартных библиотек, нескольких основный фреймворков.
Для джунов в JS требования зачастую несколько меньше.

Да такие же у джс требования, просто специалистов в этой сфере кот наплакал

Это очень спорно, относить данный вопрос к базовым или нет. Если исходить из того, можно ли без этого знания работать в-принципе — то наверное можно, и тогда это действительно не базовые понятия. Особенно если учесть что во фронте длинные цепочки промисов не часто нужны. Но с другой стороны — оно намного лучше когда такую тонкость знаешь и сразу пишешь так чтобы не наступить на грабли.

Как здесь уже писали,

The difference is subtle, but extremely useful. Promise rejections skip forward to the next then() with a rejection callback (or catch(), since it’s equivalent). With then(func1, func2), func1 or func2 will be called, never both. But with then(func1).catch(func2), both will be called if func1 rejects, as they’re separate steps in the chain.

Это значит что в случае второго аргумента к then, если в первом аргументе возникнет ошибка, она не поймается. В случае catch — поймается. Можно взять себе за правило всегда использовать catch, и не задумываться об этой тонкости. Кстати, если catch (или второй аргумент then) вернет промис — цепочка не прервется. Иногда (изредка) это удобно использовать.

На второй вопрос — наверное если errorHandler выбросит исключение. Но тут я уже не очень уверен — не сталкивался на практике с unhandledRejection.

В целом, вопросы конечно чтобы «завалить» — даже вон сырные синьоры доу не все знают ответ. Об этом обычно в разных мануалах и статьях пишут мало и поверхностно, но на практике вылезти может, поэтому занать не бесполезно

Это не сахар, это сыр. Вкусный, кстати.

Тебя просто собеседовал дурак — вот и весь сказ. Если ты не архитектор проекта, тебе это нахер не сдалось — писать ты будешь ровно в том стиле, в котором пишет вся команда. По логике разницы нет никакой, есть разница в удобочитаемости кода. А это прежде всего — весь код написанный в одном стиле.

Сами промисы — геморой редкостный с точки зрения удобочитаемости, особенно вложенные, не говоря уже о зацикленных. Мозг просто запутывается в том, что находится в данных конкретного промиса, со всеми последующими багами.

Как правило, структуры if — elseif — else и им подобные читаются на порядок легче. Почему?
1) Мозг отсекает всё лишнее, он прекрасно обучен контекстной фильтрации, это его нативная способность.
2) Покрытие логикой чётко видно, не важно, что там ничего другого и не могло быть (в теории), важно что здесь и сейчас не нужно вспоминать и гадать — вот оно, на блюдечке, в коде!

Если у промиса приличный кусочек логики, то приходится делать финт ушами: сохранять логику (фнукцию) или сам промис в переменную, которой уже пользоваться в дальнейшем. С точки зрения кода конечно ересь, но языки программирования не для кода пишутся — они для людей! И мозг отлично понимает ИМЕНА переменных, и правильно данное имя — залог читаемости кода.

Только дурак или конченая сволочь может собеседовать с целью завалить. Не нужен человек — хера ты его на собес зовёшь? Вот когда компании перестанут оплачивать саму имитацию бурной деятельности, а будут платить только за результат — ой, сколько «вахтёров» пойдёт искать работу!

Зачем писать простыню ереси, если сам не понимаешь о чём речь? Прикинь если каждый фронтендер будет со своей экспертизой отвечать на вопросы по Java?

Буває. Мене іноді реально плющить від спроб зрозуміти логіку promises, якщо вона хоч трохи нетривіальна. При тому що в Java також можна реактивне програмування юзати. Але при JS приходиться собі нагадувати: а ніззя окремий потік, ця фігня однопоточна, по іншому не можна і т.п. Стара собака, нові фокуси :)

Уже промисы не в моде, сейчас observables все хотят, а там помимо success и error есть понятие completed, которое может менятся операторами, и некоторые на него зависят, а оно само по себе не очень явно

Що значить «можна»? Є задачі які інакше як через реактивне програмування не вирішуються. І це сталося задовго до того як Java на світ народилася.

А на JS не раз ловив здивований визг браузера «а що таке volatile??»

Я могу задать буквально 3-4 вопроса по Java, чтобы сказать готов человек писатькодбля, или будет говнокодить чупакабру из шаблонистых шаблонов.

Я понимаю о чём речь, и разумеется держал в руках JavaScript. Я не берусь сказать что у меня огромная экспертиза во фронтенде, но на уровне вменяемой бизнес-логики проблем не вижу. И уж точно не вижу проблем с промисами по сравнению с их основным головняком — паршивой читаемостью готового кода, от чего растут баги у кодеров.

То что подобный вопрос задавался устно — либо дурость, либо наглость собеседующего. Вот ты за компом с интернетом умный, а попробуй пройти собес у подобного супермозга, что-либо недавно выучившего — поймёшь насколько глубока задница.

А если фронтендер будет знать ответы по Java — могу только поздравить с фуллстеком. Даже если он будет знать 25% ответов по Java, ему ровно ничего не стоит узнать остальное, если в команде есть разраб по Java.

Я же почему и говорю — нет ровно никакого резона СПРАШИВАТЬ то, что не является критическим в работе. Эти вопросы не покроют и 0.1% требуемых знаний, зато запросто отсеют нужных людей, которые с вопросом просто не сталкивались. К примеру, если человек всегда писал правильно — какой смысл спрашивать его как было неправильно, он не знает! А если писал неправильно — проще дать приказ писать вот так-то и вот так-то, а не угадывать как он привык.

А насчёт ереси — давай-ка сам расскажи, чем на работе занимаешься. Как часто ты делаешь то, чего не знаешь (и лезешь в документацию). Как часто ты оказываешься неправ, и потом переписываешь сделанное? Как часто уже через год ты не помнишь то что сам писал и зачем? И всё-таки ради интереса, пройди несколько собесов — просто чтобы понял, что твоё умение писать код львиная доля интервьюеров вообще не оценивают!!! А то что они спрашивают — релевантно как ласты на танцполе.

И уж точно не вижу проблем с промисами по сравнению с их основным головняком — паршивой читаемостью

Учитывая асинхронность многих функций — или промисы, или callback hell (и hell в этом случае — не преувеличение)

Вот ты за компом с интернетом умный

На первый вопрос я ответ знаю и без интернета, так как с промисами работаю каждый день на фронте и немножко игрался с нодой, где асинхронна вообще почти каждая функция. На второй (про unhandled rejection) — у меня есть только предположение, на практике я с таким не сталкивался.

а попробуй пройти собес у подобного супермозга, что-либо недавно выучившего — поймёшь насколько глубока задница.

Когда первую работу искал — проходил, да. В 2008 спрашивали про хранимые процедуры в mysql. К счастью не взяли туда где спрашивали. Уже потом мне стало понятно, что это были вопросы чтобы показать кто тут умный, а на практике никто и не знал как хранимки писать, и в 99% случаев их писать не нужно. Но я джуном был, а хранимые процедуры экзотикой. А вот промисы в 2017 экзотикой не являются даже на фронте. А уж в ноде и подавно. Не, ну в-принципе если ТС джун — можно в первый день работы сказать «пиши только так, некогда объяснять», но хотелось бы чтобы программист на жс такие вещи всё-таки знал. Так что вопрос легитимный, в отличии от «что будет если сложить объект с массивом» (будет хрень, и это логично, а какая конкретно неважно) или «какие параметры принимает дикаяФункцияКоторуюНиктоНеИспользует» (есть google и MDN).

А насчёт ереси

Поясняю про ересь. Ты писал:

Сами промисы — геморой редкостный с точки зрения удобочитаемости, особенно вложенные, не говоря уже о зацикленных. Мозг просто запутывается в том, что находится в данных конкретного промиса, со всеми последующими багами.

Как правило, структуры if — elseif — else и им подобные читаются на порядок легче.

Ересь в том, что промисы не имеют отношения к if/else. Они были созданы и используются чтобы заменить коллбеки, потому что если вложенности нет — читается всё ок, если в колбек вложен другой — уже так себе, а если вложить ещё один — получается ад. Ну и ТС пришел наверное понять как ответить правильно, а не чтобы его пожалели и рассказали что собеседующий мудак.

В человеческих языках выбор куда больше, чем два варианта. Проактор, реактор, async/await, coroutines, зеленые потоки, или вообще цикл с явным конечным автоматом.

В человеческих языках абсолютно любое слово является шаблоном. Что лишний раз доказывает, что все эти шаблоны проектирования — не иначе как модное дрочево на выдумку кодеров из авторитетной фирмы.

О том что не существует никаких провидцев — зачем знать? Такие же программисты, придумавшие модель как им удобно делать то, что писалось именно в те года. А что программирование вообще-то охватывает львиную часть человеческой деятельности, и код должен исполнять алгоритмы предметной области — так это теоретикам вообще неинтересно. Они привыкли, что если как попугаи кукарекать вслед авторитетам — их сочтут умными.

Если промис короткий — это читаемо. Но как правило на коллбэках висит огромный пласт логики. В результате момент вызова и момент обработки исключительных ситуаций — разнесены как коленки Саши Грей.

Собеседующий мудак уже потому, что подобные вопросы на словах не задаются. Как минимум распечатка кода нужна. В отличие от кандидата, который должен уметь только то зачем его берут, интервьюер должен владеть технологиями проведения собеседования, уметь отсекать всё лишнее за счёт СВОЕЙ готовности.

Что сделал этот клоун: выставил свою неготовность как неготовность кандидата. В результате не смог протестировать, и вместо собеседования — подрочил себе ЧСВ. Вот только не говори, что распечатать код на бумажке или предоставить ноутбук — нужно быть гуру международного класса. Наоборот! Неумение работать с людьми должно быть барьером к такой работе вообще.

К нашим баранам: казалось бы, если промис получается длинным, значит надо в промисе оставить базовую обработку, а всё остальное вышвырнуть в функцию. Красота! Но ровно до тех пор, пока в отрыве от промиса не встречаешь эти функции, пытаясь понять что они делают и куда гадят. Я это сплошь и рядом вижу. Особый шик — это как именуют эти функции. И писк моды — разнести функцию и промис по разным файлам.

Как я делаю: Создаю не метод, а промис, в отдельную переменную, со вменяемым именем и с пояснением зачем он. То есть де-факто тот же самый коллбек, но красиво обёрнутый в промис, так чтобы ежу понятно было, что иначе как внешними данными сей клитор не стимулируется. И разумеется, все исключения которые могут возникнут в процессе обработки данных — там же и ловятся, и никуда дальше не идут. Впрочем, бывают и исключения — при финансовых транзакциях имеет место пирамидальная блокировка интерфейса, и эксепшены должны всё же быть пересозданы, дабы интерфейс кошерно разблокировался.

Сами промисы — геморой редкостный с точки зрения удобочитаемости, особенно вложенные, не говоря уже о зацикленных. Мозг просто запутывается в том, что находится в данных конкретного промиса, со всеми последующими багами.

Это потому, что ты думать не хочеш. Промисы банально понимаются, если представлять действия в них графически, в виде блок-схем или любого аналога. Да-да, стрелочки рисовать.

Если есть в них проблемы, то в неровности API — например, что начальная функция и продолжатели по then/catch должны отдавать свои результаты заметно по-разному. Но не в самой идее связи callbackʼами.

Если у промиса приличный кусочек логики, то приходится делать финт ушами: сохранять логику (фнукцию) или сам промис в переменную, которой уже пользоваться в дальнейшем.

У него и так вызываются функции, и что?

Как правило, структуры if — elseif — else и им подобные читаются на порядок легче. Почему?

Потому, что ты к ним привык.

Если у промиса приличный кусочек логики, то приходится делать финт ушами: сохранять логику (фнукцию) или сам промис в переменную, которой уже пользоваться в дальнейшем.

Приведи пример. Пока слабо понятно, что тебе в этом «не так».

Только дурак или конченая сволочь может собеседовать с целью завалить.

Это другая тема, и я был бы в принципе согласен, но in practice they differ.
Например, заказ на человека сделал Вася, а собеседует его подчинённый Петя, у которого совсем другие интересы в фирме (и первый из них — подсидеть Васю). Впрочем, ты и так сам всё знаешь, только чего-то сейчас не хочешь вспоминать...

There's nothing special about catch(), it's just sugar for then(undefined, func), but it's more readable.
The difference is subtle, but extremely useful. Promise rejections skip forward to the next then() with a rejection callback (or catch(), since it's equivalent). With then(func1, func2), func1 or func2 will be called, never both. But with then(func1).catch(func2), both will be called if func1 rejects, as they're separate steps in the chain. Take the following:

developers.google.com/...​s/promises#error_handling

Не нужно сильно переживать, это нормально, у вех случаются «затупы» на интервью, в следующий раз получится.

Для интереса, напишите список вопросов интервью, думаю будет интересно тем кто готовится.

Это «нормально» только на постсовковом пространстве — спрашивать на собеседовании то что сам только что где-то вычитал. Тем более спрашивать без примера кода.

Как раз гнать разные по природе ошибки в один catch, чтобы потом их там как-то отличать — вот это антипаттерн. Просто срать в один лог все ошибки без разбора — за это надо наказывать типичной задачей — найти в логе на 200Мб за сегодня что необычного по сравнению с предыдущими 2Гб за вчера.

Это «нормально» только на постсовковом пространстве — спрашивать на собеседовании то что сам только что где-то вычитал. Тем более спрашивать без примера кода.

Для мидла, претендующего на рейт > 20$ это нормальные вопросы, сейчас информация доступна как никогда, нужно прокачивать себя, разбираться в деталях.
Да, раньше было проще ))

А кто тебе таки сказал, что люди изменились биологически?? То что информация доступна — не значит что она применяется ежедневно. А то что ты ежедневно не применяешь — ты и не знаешь, ты находищь при необходимости.

И когда ты практик, ты точно не знаешь 5 способов поменять местами значения переменных без промежуточной. К примеру, я знаю 0 способов это сделать, я создаю промежуточную переменную. А тем кто попробует сделать иначе, могу зарядить в табло ботинком, просто за то что код абсолютно нечитаемый, то есть придётся переделывать — и переделывать не маленький кусочек, а жирный кусок логики, просто потому что где-то что-то глючит, и непонятно как оно должно было работать.

Вот как раз на завтра у меня подобная задача с чужим кодом. Даже браться сегодня не решаюсь, знаю что целый день уйдёт.

И когда ты практик, ты точно не знаешь 5 способов поменять местами значения переменных без промежуточной.

Спочатку треба спитати, а що за задача, що в ній вимагається така операція?

в сортировке пузырьком может пригодится.

чтоб местами поменять соседние элементы.

Зачем это делать когда можно поменять старым добрым способом через временную переменную?

не понял вопроса. да, можно.
обмен переменными вообще мало, где можно притулить. я отвечал в контексте «вообще может использоваться». а тут, внезапно, «а зачем, если можно...»

Вопрос в том, зачем в сортировке пузырьком обмен двух переменных именно без промежуточной переменной, когда то же самое быстре делается с промежуточной

(читайте внимательнее сообщение на которое вы отвечали — dou.ua/...​rums/topic/22670/#1252329)

как это — быстрее?
быстрее, чем a,b = b,a?

Типичная реализация кода «a, b = b, a» внутри себя будет использовать две промежуточные переменные, так что это по любому не будет лучше.

писать быстрее.
читать быстрее.
понять быстрее.
выполняется медленнее, допустим.
но в каком случае в эту операцию упирается производительность алгоритма? я не могу представить.

и насчет двух дополнительных переменных мне кажется спорным.
a = c не использует же еще одну дополнительную переменную, правда?
ps впрочем,в джс так не получится, надо будет
[a,b] =[b, a]
таки одна дополнительная переменная.

но в каком случае в эту операцию упирается производительность алгоритма? я не могу представить.

Например, когда копирование дорогое (вполне возможно, если элемент не плоское данное, а объект с поведением). (Тут я не ограничиваю контекст JS’ом или чем-то ещё типовым.)

Алгоритмы для подобного случая или проектируются на минимизацию обменов, или вообще (метод универсального ножа) работают с указателями, а если нужно копирование, то делают его один раз по результату сортировки.

и насчет двух дополнительных переменных мне кажется спорным.
a = c не использует же еще одну дополнительную переменную, правда?

Не использует. Но вот подобное множественное присвоение чуть менее чем всегда требует, чтобы результат был идентичен тому, что вначале накопировали все правые части, а потом присвоили всем левым. Иначе оно такое не имеет смысла и достаточно присвоения по одному.
А дальше вопрос, кто как может оптимизировать это. Но я бы тут не рассчитывал на интеллект компиляторов.

ps впрочем,в джс так не получится, надо будет
[a,b] =[b, a]
таки одна дополнительная переменная.

По сути это те же две — маскировка в виде списка не меняет сути.

теоретически я полностью согласен:
для языка без возможности доступаи присвоения по ссылке обмен значениями переменных будет болезненным. при любой реализации(хоть промежуточная переменная, хоть syntax-based).
в знакомых мне условиях(js, C, PHP etc) чем короче и однозначней запись — тем лучше.
и пляски с XOR будут проигрывать вообще всему, а «a, b = b, a» будет на первом месте.
если у вас иной опыт, поделитесь, плиз

Разве ошибки не должны ловиться через catch?

уже прочитал ответ ниже со SOF :-)

Да. Я уже прочитал. Но спасибо.

Цікаво що ви зрозуміли з прочитаного. Яка між ними різниця? Чому один кидає помилку, інший ловить?

До речі, користувач, чия відповідь позначена як «прийнята», поганенько пояснив суть, заблукав кудись...

Там ясно как Божий день все, что еще объяснять?

Там заплутано написано і не пояснено суті. Гляньте мій код нижче, там я позабирав усе зайве. Заберіть один із then і помилка не зловиться, але коли є два then — помилка ловиться.

Если человек знает что .catch это про сахар то все будет ясно, конено если у человека есть мозг

Чіткіше буде зрозуміло, коли розберетесь чому ось таке доповнення того проміса, чия помилка не ловилась спочатку, тут ловиться:

Promise.resolve()
.then
(
  () => { throw new Error('Error number one occurs'); },
  err => console.log('catch by number one handler', err)
)
.then
(
  () => { throw new Error('Error number two occurs'); },
  err => console.log('catch by number two handler', err)
);

Можливо тому, що це різні помилики, і вони мають оброблятися по-різному? Так, декілька try-catch, якщо є очікувана помилка.

А от генерація помилки — то вже моветон. Хоча є проекти де саме така поведінка є загальним стилем. Але їх меншість. В більшості випадків, якщо проміс відловив очікувані дані, а не код помилки — то then вже сам має розбиратися із тими даними, а не напрягати зовнішній catch.

Лише говнокодери тупо кидають в лог всі помилки, не розбираючись що в них. Найти потім щось в тих логах, особливо коли не знаєш що шукати — ото вже гємор!

На скільки я зрозумів, ви думаєте, що ось цей мій код — є рекомендацією, так? — Ні, насправді я очистив той код, що у прикладі від зайвих частин і додав до нього другий then лише в якості наглядного прикладу «Що відбувається в коді».

Якщо ви запустите цей код, то логи чітко показують з якого саме then помилка кидається, і який catch їх обробляє.

У прийнятій відповіді на Stackoverflow чувак хоча й правильно спочатку сказав про:

Your first piece of code wont catch the error because the error handler is in the same .then where the error is thrown

Але далі його понесло злегка не туди, бо різний ситаксис, про який він говорить, не пояснює суті. А суть я спробував пояснити ось тут stackoverflow.com/a/48011082/1716560

Та це зрозуміло, що сама суть питання — бюрократична. Тобто оце — кошерні патерни, оце — некошерні, а те що реально буде написано — індусу не насниться.

О боже, Льоха, лише ранок почався... чи це ще з учора залишилось? Так довго не пускає?

Та нє, я просто читав реально як його пишуть, при тому пишуть люди які вже багато років тим займаються. І мене відверто дивують прецеденти, коли валять кандидата на питаннях «з якого кінця яйця бити».

А суть я спробував пояснити ось тут stackoverflow.com/a/48011082/1716560

кстати, это тоже неверно. Точнее, это верно только в случае resolve.
В случае реджекнутого промиса:
— rejectedPromise.then(onSuccess, onError) вызовет только onError
— rejectedPromise.catch().then() вызовет по очереди и .catch, и .then

rejectedPromise.catch().then() вызовет по очереди и .catch, и .then

Чому ви так думаєте? Десь про це говориться у документації, чи може є приклад, що про це свідчить?

Я не думаю, а знаю. Для этого достаточно просто понимать, как работают промисы.
Понятия не имею, говорится об этом в докуметации или нет.

Пример? Могли бы и сами проверить.
Но если лень, то возьму ваш пример и заменю resolve на reject

Promise.reject(’some error’)
.catch
(
err => console.log(err)
)
.then
(
() => { throw new Error(’Error occurs’); }
)

И что будет в консоли? Правильно: сначала «some error», а потом «Error: Error occurs»

А, ви говорите про режект самий перший, я думав що в then. Так, якщо самий верхній режектиться, то вони не ідентичні. Я ж написав приклад з першим резолвом...

А, ну все норм — к общему мнению пришли. Сори за излишний гон, если что :)
Да, в случае резолва катит, но вот с реджектами интереснее все :)

Ну а что тут такого — catch создал новую ветку обработки ошибки в виде промиса. Теперь в этой ветке свои успехи и неудачи, ничуть не связанные с исходными...

В промисах вообще ничего «такого» нету, но постоянно кому-то что-то непонятно (зачастую базовые вещи) :)

охохо, в эту фразу вообще почти любую концепцию можно подставить и будет справедливо

Коментар порушує правила спільноти і видалений модераторами.

Підписатись на коментарі