Pull to refresh

Comments 58

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

ИМХО зачастую достаточно адекватной индикации работы, чтобы интерфейс не раздражал. Вот на хабре, например, при голосовании за топик/комментарий/карму случаются задержки, которые несколько раздражают. Я то вроде как нажал на элемент (или промахнулся? как понять?) а никаких изменений нет. Я то могу открыть firebug и посмотреть ушел запрос или нет и откуда задержка. Но просто пользователи этого делать не будут. Они нажмут еще раз и еще. И получат уведомление о том, что повторное голосование запрещено. Хотя его то не было. Индикация решила бы подобные проблемы.

Решение этих проблем — вот это важно. Важно держать пользователя в курсе происходящего и не пытаться бежать впереди паровоза. А ускорение интерфейса в угоду предсказуемости, имхо, небезопасно.
На самом на примере с хабром имеется «ни то ни се». С одной стороны — интерфейс не обновляется пока запрос не завершится успешно, с другой — интерфейс и не блокируется, позволяя нажать еще раз и увидеть неожиданное сообщение. Это, конечно, очень простой пример, но и тут можно применить предложенную технику — обновить интерфейс сразу же и дело с концом. Остается только решить вопрос с ошибкой при выполнении запроса. Как и было сказано — такое случается относительно редко и варианты решения есть.
В случае с хабром — достаточно просто показать, что запрос выполняется. Блокировать элементы тоже нужно, конечно. Но главное — индикация.
С сервера, кстати, приходит ответ с новым значением. Так что просто изменять текущее значение в нужную сторону — некорректно.
Обработка ошибок «потом», откат действий — совсем не прост, а в случае если действий было несколько последовательных, то там вообще такой мрак начнется, что мало не покажется. А все ради чего?
Все ради пользователей — конечно.

Да, блокировка интерфейса (конечно с индикатором) — это тоже вариант. Новое значение с сервера вас не очень то интересует, ведь если вы не голосуете — новые значения не подтягиваются по таймеру. Опять таки, в этом примере, особой обработки ошибок не нужно — достаточно показать вам алерт что «не получилось». В общем случае, обработка ошибок и (возможно) откат изменений — это действительно сложный вопрос для предложенной архитектуры. Но вопрос и интерестный и достойный того чтоб искать на него ответы (возможно общее решение, а может решения для каждого конкретного случая).
Ради пользователей — логично, а ради чего именно?

Просто поменять интерфейс, не получив реальных данных… это рискованный вариант. Годится только для простых случаев, когда вы можете в случае ошибки сказать «Извини, чувак, облом вышел. Твое голосование превратилось в тыкву, начинай заново».

Как пример такой работы и ее последствий можно взять ICQ или аналоги — набранное и отправленное сообщение выводится на экран и воспринимается как доставленное, но отправка не означает прочтения. Поэтому люди сами спрашивают подтверждения — «Я кинул ссылку. Дошла?». Вы предлагаете отправить запрос на сервер, сказать пользователю что все Ок, и только потом спросить у сервера «Мы тут с пользователем письмо отправляли. Все Ок? Ты получил?».

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

Если первое, то все хорошо — наша сессия с сервером в полном порядке и мы можем писать дальше. Если второе, то незачем создавать у пользователя иллюзию нормальной работы. Пусть лучще знает сразу, что все Ок или не Ок. Это позитивнее, чем написать трактат и только потом узнать, что с ним облом вышел.
От «пользователь закроет страницу» уже давно есть лекарство, которое используется в том же GMail, и ещё много где.
Пока есть незавершённые POST-запросы, при попытке закрыть страницу, выводится сообщение «Есть незавершённые действия, действительно ли вы хотите уйти», которое реально работает.
Ну во первых не всегда есть возможность спросить хочет ли пользователь уходить. У него может вылететь браузер, перезагрузиться комп или он вообще может просто обычным порядком его выключить не закрывая вкладки (я так делаю). И запросы оборвутся, сообщения о том, что что-то пошло не так пользователь не получит. Просто не будет и думать, что что-то могло пойти не так. Интерфейс же отреагировал.

По поводу вопросов «действительно ли вы хотите… ?» — наглядно видно как это работает когда пользователи удаляют нужные им файлы, дропают базы и так далее. Не всегда это работает.

Так что лекарство весьма спорное и работать оно, увы, будет не всегда.
> По поводу вопросов «действительно ли вы хотите… ?» — наглядно видно как это работает когда пользователи удаляют нужные им файлы, дропают базы и так далее. Не всегда это работает.

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

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

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

Да, возможно вы и правы, но полагаться на это я бы не стал. Кто-то не прочитает, кто-то будет только раздражаться тем, что поведение отличается от ожидаемого. Это тоже отрицательная сторона. А у кого-то это и вовсе может не отработать. Вон про Оперу пишут в топике.

>>Обычно асинхронный запрос не больше секунды длится
Вот. Обычно. Поставить таймаут на секунду — опасно. На медленном канале, который еще и загружен легко может быть и 3 секунды задержки + время ответа сервера. Уже получается достаточно много, чтобы считать, что все прошло хорошо. Если человек нажал на элемент, тот отреагировал, а через 5 секунд все погасло (пользователь еще может не увидеть сообщения об ошибке, например), то вряд ли он будет сомневаться в отправке. 5 секунд — большой срок.

>>В общем, в большинстве случаев плюсов больше, чем минусов.
Я не вижу каких-то таких плюсов, которые бы позволили списать эти серьезные минусы. Сам я подобные техники в работе применяю, но только после тщательнейшего обдумывания и только в наименее ответственных местах. И уж я точно не стал бы утверждать, что это можно применять повсеместно и что за подобным подходом «будущее интерфейсов».
Конечно, это не будущее интерфейсов :) Просто один из подходов.
Можно просто создать taskbar, где и будет отображаться эта очередь, в чем проблема? Да пользователь будет видеть, что его действия еще не возымели силу, но, они приняты в обработку, увидел в taskbar'е нолик — можно закрывать.
Можно, конечно же. Можно создать таскбар, потом к нему написать легенду, что конкретно означает каждое состояние и справку, что до завершения всех запросов страницу закрывать нельзя. И что у нас получится в итоге? Место на экране занимает, расположен далеко от элементов которыми управляешь, еще и история какая-то… Зачем это все? Это далеко не упрощение и не улучшение, а осложнение интерфейса и обременение человека какими-то непонятными и совершенно ненужными ему вещами. Просто сравните это с нормальной индикацией.

С тем же успехом можно рекомендовать установку firebug-a и слежению за запросами с его помощью. Только, боюсь, сайт такой популярностью пользоваться не будет, несмотря на быстро реагирующий интерфейс.
Усложнение? Вообще-то такая вещь как таскбар, воплощает один из основной принципов ui — одной точки входа, зачем для каждого элемента городить огород в виде кнопки с индикацией загрузки, окна с индикацией загрузки, иконки с индикацией загрузки? Да и суть по-моему не в этом, а в том, чтобы не блокировать весь интерфейс на время выполнения одной задачи. Ведь можно в случае ошибки с доставкой сообщения, снова развернуть окно с редактированием сообщения, чтобы пользователь мог скопировать текст или повторить действие.
Когда я сталкиваюсь с тем что ui заблокирован, запрос явно не проходит, а у меня перед носом только иконка-прелоадер, чем это лучше, как и откуда мне извлечь данные которые не ушли, почему в это время я не могу поменять цвет рабочей поверхности или переименовать папку?
>>зачем для каждого элемента городить огород в виде кнопки с индикацией загрузки, окна с индикацией загрузки, иконки с индикацией загрузки?

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

>>чтобы не блокировать весь интерфейс на время выполнения одной задачи.

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

>>как и откуда мне извлечь данные которые не ушли
Точно так же по таймауту запрос не будет выполнен, вы получите свою форму и свои данные назад. Не вижу связи вообще.
Хорошо. Проголосовали вы за 10 из 200 комментов, все с ошибкой, как быть?
Нет в этом месте отличий с асинхронным интерфейсом. Совсем. Просто при асинхронной обработке интерфейс сначала говорит, что все прошло хорошо, а потом уже может сказать, что произошла ошибка (что само по себе странно, правда?), а синхронный интерфейс будет ожидать окончания операции и потом либо произведет действие, либо сообщит об ошибке.

Обработка ошибок никак не отличается в этих ситуациях.
Как быть когда вы проголосовали за 10 комментариев, показали что все прошло отлично, а потом оказалось, что сервер недоступен? В случае синхронного интерфейса достаточно будет уведомить об ошибках и не производить изменений с элементом, а в случае с синхронным — откатить изменения назад.
Давайте другой пример, допустим, вы добавили запись в адресную книгу, что вам мешает, поставить индикатор на эту запись, но при этом отобразить ее в общем списке? Произвести с ней действия на клиенте, а потом на сервере?
Давайте теперь возьмем веб-приложение с полной перезагрузкой страницы: вчера вы открыли сайт и оставили его в браузере, сегодня пришли и не перезагружая страницу решили добавить комментарий, пишете, жмете «отправить» и вам вылезает 404 ошибка, потому что ночью страницу удалили. Асинхронно? Абсолютно! Тогда какая разница где создавать очереди на клиенте или на сервере? Главное обеспечить грамотный механизм транзакций, откатов и сохранения изменений.
>>что вам мешает, поставить индикатор на эту запись, но при этом отобразить ее в общем списке?
Если будет однозначно понятно, что запись добавляется, а не добавлена — то это по мне. Это хорошо. А если она будет сразу добавлена и асинхронно добавляться на сервере, то это та асинхронность о которой я говорю. И мне кажется не только я. Речь о том, что запись будет добавлена как будто уже все случилось, хотя на самом деле в фоне только ушел запрос.

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

>>Тогда какая разница где создавать очереди на клиенте или на сервере?
Разница в том, что очередь на клиенте не контролируема, может быть прервана, утрачена и пользователь должен четко понимать, что действие НЕ выполнено до сих пор, а это уже синхронность. Иначе будут потери, будут откаты и все остальные неприятности.

Чего стоит откат операции, которая уже была объявлена как совершенная. Хуже сложно себе придумать. Нечто вроде «ваш платеж проведен», а потом «в процессе работы возникла ошибка, платеж не проведен». Жуть!

Не подумайте, что я против подобных интерфейсов, я вот прямо сейчас применяю асинхронный подход, но в том месте, где это в самом деле актуально и где потеря данных не приведет ни к каким проблемам. А бездумное и неуместное применение подобного подхода — опасно и может привести к серьезным проблемам. Не везде это можно применять и далеко не всегда.
Хорошая идея. Только я бы внёс поправку.
Индикация загрузки как в гугле(надпись «загрузка»), при наведении можно выдавать очередь типа
— отправка письма;
— создание метки;

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

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

Конечно. Он может просто закрыть окно браузера и потерять так и не отправленное письмо со святой уверенностью «оно уйдёт»
Даже не «уйдет» — это еще оставляет какое-то поле для сомнений, а пользователь будет думать, что письмо уже ушло. Т.е. действие произошло, все получилось, все отлично.
а если прочитать статью целиком, можно заметить абзац про onbeforeunload (чем кстати и пользуется gmail)
Смысл в том, что пользователь асинхронного интерфейса теряет связку между своим действием и моментом исполнения действия. Необходим ack — и onbeforeunload _ничем_ не поможет.
никто не обещает, что у тебя browser local storage достаточен для сохранения всего черновика.
никто не обещает что в куки влезет.
никто не обещает, что он _вообще_ будет вызван.
черновика чего? кто влезет в куки? кто будет вызван, onbeforeunload?
конечно же критически важные операции нужно выполнять синхронно, и только. но много ли в вашей системе таких операций?
а еще можно погуглить и узнать, что onbeforeunload не поддерживает опера, например.
И что, там при закрытии страницы с порно не вылезает предупреждений и не открывается новых окон? Может там ещё и блокировка всплывающих окон работает? Опера, кажется я снова люблю тебя!
Сама по себе идея неплоха, но имхо является крайностью всё-же. Надежность не высокая, пусть ускорение на 0.1с прибавит 1% пользователей, но сколько пользователей не отправят товар в корзину из-за сбоя где-то в браузере?
Есть неплохая альтернатива стандартному подходу — мы как-то реализовывали, заказчик остался на полном позитиве.
Смысл в том, что запросы все-таки уходят на сервер, уходят на специальный адрес типа superfast.site.ru (у нас был отдельный сервер под это дело, дешевый, но отдельный!), по которому тупейший безфреймворчный скрипт делает примитивную запись в базу этого запроса и тут же возвращает ответ юзеру. А сайт уже отдельно обрабатывает эти запросы, отдельным скриптом (постоянно висящим в памяти и мониторящим это дело). Что имеем в позитиве — офигенно высокую скорость ответа (почти на 2 порядка быстрее обычного ответа полноценного сайта) для юзера наряду с уверенностью что данные до сервера дошли и будут обработаны (а куда они из логов-то денутся), плюс запись всех логов действий пользователей (мало ли кто взломом пытается баловаться). Более того, это положительно сказалось на целостности данных (абсолютно нет конкурентности в запросах на изменение, все операции суть последовательны, эдакий механизм транзакций встроенный нахаляву).
А какого рода был сайт, если не секрет? Форум, магазин, что-то еще?
Помесь форума/социалки со специфического вида документооборотом. Изначально основной задачей было повысить скорость заполнения/редактирования кучи однообразных документов разной степени связности (заполняли разные люди в регионах, иногда с не очень хорошим интернетом). Потом было требование сделать мгновенной личку (отправил и пошел дальше), т.к. всё корпоративное общение шло уже на сайте, это было проще чем строить общую сетку по регионам, а частота переписек в личке дошла уже до уровня чатов. И где-то после этого этапа система уже была настолько интегрирована в движок сайта, что было решено добить и все остальные функции.
На самом деле эффект потрясающий, вместо стандартных 0.2-0.5 секунды только на генерацию страницы в старом движке, в новом механизме все операции стали занимать 0.01-0.03 секунды, что визуально воспринималось как «вообще без задержек». А учитывая что уменьшились и размеры ответа на запрос (т.к. отказались от возврата подробностей операции в аякс ответе), всё вообще стало просто реактивным.
Сделать как в статье — посылать данные «пост-фактум» — изначально мысль была, но отпала именно по причинам не всегда хорошего интернета в регионах, во всеми вытекающими. Слишком много получалось дублей данных или невосстановимых ошибок.
«На самом деле эффект потрясающий, вместо стандартных 0.2-0.5 секунды только на генерацию страницы в старом движке, в новом механизме все операции стали занимать 0.01-0.03 секунды, что визуально воспринималось как «вообще без задержек»»

Можно немного поподробнее, что меряли, как меряли?
Меряли чрезвычайно банально php => microtime, в самом начале и в самом конце. Понятно, что кроме времени генерации было еще время на посылку запроса/прием ответа, но эффект всё равно был очень хорошим.
Но ваш подход не будет работать, если действие может быть ошибочным (та же проверка уникальности).
Тут главное сохранить изменения, чтобы потом вернуть их пользователю со словами: «Гена, помнишь, ты просил меня принести полотенце...» «Письмо не было отправлено, попробуйте еще раз»
В принципе это проблемы не составляет. Достаточно при любой следующей загрузке страницы вывести юзеру надпись мол «одно из ваших предыдущих действий не прошло», данные-то о действии есть все, можно даже страницу с уже заполненными данными показать из той же базы логов. У нас просто такой необходимости не было.
Мне кажется, такую асинхронную работу можно совмещать исключительно с Local Storage. При этом при следующей загрузке приложения проверять, есть ли в LS не проведенные операции для сервера.

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

Банально может свет отрубиться, и очень очень важное письмо так и не отправилось, хотя пользователь думал обратное.
Мне вот интересно, а информация в локал сторэйдж записывается на диск сразу после поступления, или же сначала в оперативку и периодически или при закрытии браузера дампится на диск?
По идее, сразу. Есть ещё WebSqlDatabase (SQLLite, по сути) и IndexedDb.
Как раз разрабатываю админку с похожей философией. По поводу проблем сети — это надуманно. Я решил это отдельным от интерфейса логом, где фиксируются все запросы и где их можно отправить повторно в случае ошибки, а скопировать данные можно в любой момент, они сохраняются локально сразу же после нажатия кнопки Save, не дожидаясь успешного запроса. После сохранения на сервере данные обновляются, на всякий случай. Если попытаться закрыть окно до окончания синхронизации данный вылетит предупреждение.

Настоящая проблема — это большие массивы данных. Например, уже 10 тысяч товаров — это проблема для оперативки, если речь идет о JS.

Web Database может существенно увеличить количество записей, но, тоже, не панацея, клиентский компьютер может тормозить, тем более сейчас довольно жесткое ограничение по объему. Кроме того, слишком много логики переносится во фронэнд, вплоть до orm придется реализовывать.

Local Storage — это кэш и он никак не решает основные проблемы с производительностью.

А в целом, идея не нова и пришла скорее эволюционным путем. В том же Ext JS гриды могут работать по схожему принципу.
посмотрел на демку.
поудалял все записи. Вот передо мною пустая страница. Нажимаю на F5 — и о чудо! Оказывается, что откуда-то взялась добрая пачка записей. Если б хоть изредка данные подгружались с сервера, хоть как-то, ну или был бы индикатор как на хабре информер каментов — было б намного ожидаемее их увидеть, а так — шок!
это никак не связано с обсуждаемой темой. даже если вы блокируете ЮИ для сохранения записи — будете выгружать весь грид целиком изза изменения одно записи?!
Это тоже можно делать асинхронно.
В Лаборатории Gmail можно включить функцию фоновой отправки — вроде бы работает неплохо, хотя и страшно порой (приходится лишний раз проверять факт отправки).

Хотя именно на примере Gmail меня больше раздражает ожидание загрузки прикреплённых файлов (при написании письма). Иногда бывает так, что открываешь второе окно, а о первом потом благополучно забываешь (что оно там грузится). Хотя у меня отнюдь не медленное подключение.
Год назад все это возможно было бы и ново и интересно, но сейчас это уже не опция, а распространенная практика. Ничего нового, а тем более концептуально нового в этом нет, в конце концов веб-приложения просто наследуют практики настольных приложений где также недопустимо лочить надолго интерфес.

>Алекс Маккоу разработал JavaScript-фреймворк Spine. В нём реализован концептуально новый подход

Ализарность перевода зашкаливает. Для тех кто не в теме:
Spine появился позже Blackbone и по сути его легковесная альтернатива, ничего нового не несет.
Маккой не автор MVC и не первый кто перенес его на клиент.
Книга JavaScript Web Applications суть краткий обзор современных библиотек/практик клиент-сайд разработки и тем кто немного следит за развитием HTML5 и JavaScript вряд ли даст что-то новое. Полезней прочитать документацию к тому же Blackbone.

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

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

1. Захожу в список — запрос на сервер. Жму редактировать — запроса нет. В поле Name значение из списка — т.е. не актуальное название. Косяк. Особенно актуальный косяк, если список был открыт 2 дня, а потом решили нажать редактировать.

2. Открываю список в двух вкладках. В одной вкладке жму удалить у записи, а на другой — редактировать. Открывается форма редактирования, жму сохранить — сохраняется, возвращаюсь в список. Я отредактировал удаленный элемент. Никаких предупреждений, что объект удален нет. Косяк.

3. Открываю список в двух вкладках. В обоих жму редактировать одну и ту же запись. В обоих жму сохранить. В обоих сохраняется. В обоих перехожу на список. В каждой вкладке вижу разное название. Косяк. Реально сохраняется последняя запись, никаких предупреждений, что поле Name уже было переименовано нету. Косяк.

Т.е. вот цена за выигрыш в 0.1 секунду.
В обоих


В обеих. Не стал бы придираться, но ведь Вы написали это четыре раза подряд.
Все правильно, отлаживать асинхрону значительно заковыристее, чем линейку.
Да, глядишь лет через 10 фреймворки на ЯС доберутся до современного уровня, если уже сейчас начали копать асинхронные UI.
Ура, товарищи.
Насчёт примера в gmail — можно подключить кнопку «отмена» туда — тогда можно отменить пересылку если поняли, что не туда направили письмо) и это мне кажется удобнее, чем фоновый запрос, который никак не отменить.
Это не просто нишу, это очень узкая ниша. Ведь гораздо чаще в ajax веб-интерфейсах данные не отправляются, а запрашиваются с сервера для отображения, т.е. приложение вынуждено ожидать результата, т.к. сами отображаемые данные содержатся в получаемом пакете. Если же мы много данных затягиваем в память приложения чтобы их повторно не скачивать и отображать из своего кеша данных, то встает проблема синхронизации с другими пользователями при групповой работе и вообще поддержание актуального состояния данных. В этом случае нужно делать подписку на серверные события и при каждом изменении в тех данных, что закешированы в памяти клиентского приложения, оно будет получать уведомление об этом, конечно отзывчивость приложения увеличится, но нагрузка на сервер и сложность так же возрастут, а значит упадет надежность. В общем, мгновенное отображение без ожидания имеет смысл и право на существование в настолько узком кругу задач, что использовать для этого специальные фреймворки считаю нецелесообразным, только код раздувать, а реализация без фреймворков тут не на столько сложная, чтобы париться.
Насколько я понимаю, так работают комментарии на фейсбуке. Их выдают ссылки – появляются не сразу.
Оно то, конечно, хорошо, но на отладку потребуется намного больше времени, чтобы предусмотреть все возможные варианты, которые могут привести к ошибке. Ну, если не все, так большинство. Малейшая ошибка в скрипте, JS будет делать вид, что все прошло хорошо, а запрос не обработается.
Sign up to leave a comment.

Articles

Change theme settings