Javascript-форум (https://javascript.ru/forum/)
-   Общие вопросы Javascript (https://javascript.ru/forum/misc/)
-   -   Очень странное поведение (https://javascript.ru/forum/misc/21201-ochen-strannoe-povedenie.html)

popov654 01.09.2011 04:15

Очень странное поведение
 
Только что закончил отладку нового механизма сохранения данных в БД. Столкнулся с ужасной вещью, причины которой мне совсем не понятны.

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

Открыл параллельно в разных вкладках браузера свою систему и phpMyAdmin (для 100% проверки, глюк проявлялся и без этого).

Кстати, после confirm-а появляется alert с сообщением о количестве выполненных запросов. Так вот, к делу.

Происходит ужасная вещь: если нажать на кнопки ОК в confirm и alert довольно быстро (в пределах 3-х секунд), то внешне всё пройдёт гладко. Более того, данные в базе перепишутся (phpMyAdmin тому подтверждение). После этого случится запланированный редирект. Но вот после клика на изначальной кнопке трекера - по сути, это возврат к прежнему блоку, который реализован через отправку формы на тот же адрес в качестве action, используя скрытое поле, в котором хранится сдвиг для чтения из БД (как и первый переход, они равносильны) - произойдёт переход к прежнему блоку, но данные там будут... СТАРЫЕ! :blink:

Я до сих пор не могу понять чё это за фигня. Самое интересное, что если перед подтверждением confirm-а выждать секунд 5-7, то баг не проявляется. Если пользоваться кнопкой сохранения снизу, а уж потом делать переход к другому блоку используя трекер, ничего подобного тоже нет. И самое главное, в IE7 я этого тоже не заметил.

По логике вещей, такого быть не должно.

P.S. Только что проверил, в Opera ситуация ещё того печальней. Даже длительное ожидание не помогает :( И в IE7 теперь кстати то же самое... Может, баги PHP-интерпретатора, который использует результаты старых MySQL запросов вместо того, чтобы делать новые?
Но тогда почему вариант через кнопку Сохранить проходит на ура? :-?

devote 01.09.2011 07:56

Куча текста и ничего не понятно, какие данные, что, откуда, для чего как... не понятно... куча букв и ничего не ясно.

popov654 01.09.2011 08:37

ОК, сорри. Вот адрес, уже Бог знает который раз его пишу :)

http://popov654.pp.ru/livemarks

Логин admin, пароль test

Попробуйте сперва написать что-нибудь в любой чистой ячейке, например в базе Литература, потом кликнуть по бирюзовому квадратику (откроет чистую страницу засчёт выставления оффсета большего, чем кол-во записей в таблице БД), подтвердите сохранение. Пойдёт асинхронный запрос, правда "глупый", через объект Image(). Но успешный.
Потом вернитесь обратно, кликнув по первому квадрату. Вуаля - всё осталось как было, но это только иллюзия, ибо просмотр в phypMyAdmin говорит об обратном. Можете даже туда же, только через другую вкладку зайти, повторно залогинившись. Изменения должны появиться

Что-то где-то кэшируется?.. :-?

popov654 01.09.2011 08:41

Блин... Существенная поправочка: фокус со вторым логином на другой вкладке не поможет :blink:

Но в phpMyAdmin данные реально обновляются! Я к сожалению не могу дать Вам доступ чтобы показать... Могу скрин разве что выложить :(

Вот Вы как думаете: теоретически может так быть, что данные остаются старыми только для моего скрипта?.. Ну я не знаю, веб-сервер же видит, к какому скрипту идёт обращение, да и PHP-интерпретатор тоже. Может кто-то из них чего-то сохраняет? Ну например интерпертатор мог каким-то образом старые переменные использовать вероятно... С результатами предыдущих SQL-запросов
Я не вижу другого рационального объяснения...

popov654 01.09.2011 08:48

Ну кажется начало что-то прорисовывается. При варианте, когда я сохраняю через кнопку снизу, Опера например секунд 6-7 чего-то "грузит" даже после алерта, хотя алерт идёт по коду уже после назначения нужных URL картинкам. Вот код:

function saveChanges() {
   var imgs = new Array();
   for (var i = 0; i < queries.length; i++) {
      imgs.push(new Image())
      imgs[i].src = queries[i]
   }
   setTimeout('queries = []', 100)
   alert('Данные сохранены! Выполнено запросов: ' + queries.length)
   return false;
}


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

Geddar 01.09.2011 09:29

чисто предположение, може autocomplete в инпутах виновен?
Когда назад возвращаетесь, подгружаются какие-нибудь данные или станица?

popov654 01.09.2011 10:25

Блин, я идиот. Вопрос снимается.
У меня просто не успевали отправиться ВСЕ запросы, а поскольку тип работы определялся лишь по одному ученику... Короче, этому ученику периодически "не везло". По логам веб-сервера проверил. Ну а то, что я видел в phpMyAdmin - это как раз и были те 7-10 записей из 19, что сохранились.

Вопрос: а как сделать чтобы гарантированно убедиться, что все запросы выполнены? Только XMLHttpRequest?

popov654 03.09.2011 01:56

Ау... :(

popov654 03.09.2011 02:05

Обобщу вопрос: вот есть у меня массив запросов. Ну пока это Image и я проверяю свойство complete, которое должно характеризовать завершение загрузки, причём я даже настроил PHP скрипт так, чтобы он реально, обработав запрос, отдавал изображение 1х1 пиксель.

Если у меня будет XMLHttp, то там будет onReadyStateChange...

Вопрос в другом. Вот есть у меня массив этих объектов, и пока все запросы не отправлены полностью, дальнейшее выполнение кода нужно ЗАБЛОКИРОВАТЬ, чтобы браузер не ушёл на другой адрес. Поскольку обычный setTimeout(), как здесь уже обсуждалось, не останавливает поток выполнения, я использую логическую переменную. Но получается глупо: как только один запрос отправится, она устанавливается в true. Если есть ещё хотя бы один незавершённый запрос, его "прослушиватель", повешенный на setInterval(), конечно перепишет переменную на false...

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

А вторая проблема видимо в том, что я криворукий - у меня после реализации вышеописанного вообще запросы перестали отправляться :(

Где я слажал?

popov654 03.09.2011 02:08

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

melky 03.09.2011 02:12

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

popov654 03.09.2011 02:29

Цитата:

Вопрос в другом. Вот есть у меня массив этих объектов, и пока все запросы не отправлены полностью, дальнейшее выполнение кода нужно ЗАБЛОКИРОВАТЬ, чтобы браузер не ушёл на другой адрес. Поскольку обычный setTimeout(), как здесь уже обсуждалось, не останавливает поток выполнения, я использую логическую переменную. Но получается глупо: как только один запрос отправится, она устанавливается в true. Если есть ещё хотя бы один незавершённый запрос, его "прослушиватель", повешенный на setInterval(), конечно перепишет переменную на false...

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

Цитата:

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

Я убрал лишний текст (сорри за повтор). Код приводить вряд ли есть смысл: там ровно то, что написано выше, только ещё менее наглядно (например, потому что логических флага зачем-то сделано два).

popov654 03.09.2011 05:15

Вот код функции посылки запросов:
function saveChanges() {
   var imgs = new Array();
   for (var i = 0; i < queries.length; i++) {
      imgs.push(new Image())
      imgs[i].src = queries[i]
   }
   var tries = 0;
   var timer = setInterval(function() {
      tries++
      requestComplete = true
      for (var i = 0; i < imgs.length; i++) {
         if (!imgs[i].complete) {
            requestComplete = false
         }
      }
      if (requestComplete) {
         clearInterval(timer);
         alert('Данные сохранены! Запросов выполнено: ' + queries.length)
         queries = []
      } else if (tries > 8) {
         clearInterval(timer);
      }
   }, 100);
   return false;
}

var requestComplete = false



А вот код, который выполняет ожидание перед сменой блока:

elem.onclick = function() {
      if (queries.length && confirm('Вы хотите сохранить введённые данные?')) {
         saveChanges()
         var tries = 0;
         var timer = setInterval(function() {
            tries++
            if (requestComplete) {
               requestComplete = false
               clearInterval(timer)
               document.form1.action = 'edit.php'
               document.form1.from.value = pages * 25
               document.form1.submit()
            }
            if (tries > 10) {
               clearInterval(timer)
               alert('Ошибка запроса')
            }
         }, 200);
      } else {
         document.form1.action = 'edit.php'
         document.form1.from.value = pages * 25
         document.form1.submit()
      }
   }


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

Я отредактировал неудачные на мой взгляд места, это исправленная версия. Однако на длинных запросах (29 записей сразу например) почему-то вылетает алерт с неудачной отправкой (при одиночных запросах всё ОК).

Добавлено спустя 5 минут:

Всё! Работает! Я всего лишь увеличил таймауты, и этого оказалось достаточно. Вот итоговый код:

function saveChanges() {
   var imgs = new Array();
   for (var i = 0; i < queries.length; i++) {
      imgs.push(new Image())
      imgs[i].src = queries[i]
   }
   var tries = 0;
   var timer = setInterval(function() {
      tries++
      requestComplete = true
      for (var i = 0; i < imgs.length; i++) {
         if (!imgs[i].complete) {
            requestComplete = false
         }
      }
      if (requestComplete) {
         clearInterval(timer);
         alert('Данные сохранены! Запросов выполнено: ' + queries.length)
         queries = []
      } else if (tries > 8) {
         clearInterval(timer);
      }
   }, 500);
   return false;
}

var requestComplete = false


elem.onclick = function() {
      if (queries.length && confirm('Вы хотите сохранить введённые данные?')) {
         saveChanges()
         var tries = 0;
         var timer = setInterval(function() {
            tries++
            if (requestComplete) {
               requestComplete = false
               clearInterval(timer)
               document.form1.action = 'edit.php'
               document.form1.from.value = pages * 25
               document.form1.submit()
            }
            if (tries > 3) {
               clearInterval(timer)
               alert('Ошибка запроса')
            }
         }, 4000);
      } else {
         document.form1.action = 'edit.php'
         document.form1.from.value = pages * 25
         document.form1.submit()
      }
   }

popov654 03.09.2011 05:23

Чёрт... Рано обрадовался. Два столбца по 29 запросов уже не тянет. А должна тянуть максимум 25 таких столбцов!

Самое обидное, что пока я не ввёл эти проверки, запросы шли быстрее раз так в пять. :(

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

melky 03.09.2011 12:00

вы проверяете завершенность по интервалу - ай ай ай. :nono:
function saveChanges(callback) {
    var imgs = new Array(), i = 0, max = queries.length - 1;
    
    var timer = setInterval(function(){
        imgs.push(new Image);
        
         // обработчик загрузки для последней картинки
        if (i === max) imgs[i].onload = callback( imgs );
        
        imgs[i].src = queries[i];

        if( ++i > max ) clearInterval(timer);
        
    }, 100);
}


elem.onclick = function() {
    // для экономии места повторяющиеся операции занёс в функцию
    function normForm() {
        var a = document.form1;
        a.action = 'edit.php';
        a.from.value = pages * 25
        a.submit();
    };
    
    if (queries.length && confirm('Вы хотите сохранить введённые данные ? ')) {
        
        saveChanges(/*выполнится по завершении запроса*/function(a) {
            
            // a - массив картинок.
            alert('Данные сохранены! Запросов выполнено : '+a.length);
            queries = [];
            normForm();
        });

    } else {
        normForm();
    }
}

popov654 04.09.2011 16:24

Спасибо) Не сообразил, что onload есть не только у body :)
Туплю)

P.S. Там код этого обработчика несколько сложнее (потому что при нажатии на разные кнопки вызываются разные блоки), но я допилю, всё ОК :)

popov654 04.09.2011 16:25

Жаль, плюсик Вам в карму не отправить :)

popov654 04.09.2011 19:39

Да ёлки-палки... :-E
После такой переделки у меня теперь та же ситуация, что была в самом начале: пишется, но не всё.

Я изучил лог-файл сервера. В большинстве случаев не хватает одного запроса, иногда один запрос отправляется на адрес undefined. Странно...

popov654 04.09.2011 19:46

Ну я так и думал. Надо было поменять эти две строки местами:

if (i == max) imgs[i].onload = callback( imgs );
imgs[i].src = queries[i];


Походу до присваивания src интерпретатор считал рисунок загруженным (как раз по адресу undefined), и запускался callback, очищающий весь массив адресов запроса. В итоге запрос либо не уходил вовсе, либо уходил на адрес undefined.

Настораживает ещё вот какой факт: иногда помимо запроса undefined не хватало ещё одного запроса. Поэтому вопрос к Вам: Вы уверены, что все предыдущие запросы успеют дойти? Обработчик-то мы привязали только к последнему :)

melky 04.09.2011 20:29

как они могут не дойти? картинки создаются в цикле - значит запросы так же создаются в цикле. посмотрите вкладку Network в инструменте веб-мастера

popov654 04.09.2011 21:16

Ну мало ли, кто-то будет создаваться дольше... Я на Java уже писал сетевое приложение, реально с этим делом сталкивался. Правда там запросы создавались в разных потоках... :)

popov654 04.09.2011 21:49

Всё! Победа! Наконец-то всё работает так, как оно и должно работать в идеале. Теперь я ставлю в ближайшей перспективе такие две цели:

1) Сделать красивый анимированный прогрессбар (сам прогрессбар уже есть), который будет отображать РЕАЛЬНЫЙ ход выполнения запроса
2) Сделать передачу методом POST всех строк сразу (либо в формате JSON, либо "склеив" их вручную). Это должно дать существенный выигрыш в скорости + избавить мои бедные логи от замусоривания бесконечными запросами к одному и тому же PHP-скрипту.

Есть только одна проблема: вторая задача слегка перечёркивает первую, поскольку в этой ситуации отследить реальную степень готовности вряд ли возможно... Кто что думает на этот счёт? :)

popov654 06.09.2011 01:00

Ребят, ну подскажите кто-нибудь)
Я же ведь пока делать ничего не могу, пока не определюсь, надо ли GET на POST будет заменять... :)

melky 06.09.2011 02:49

Цитата:

Сообщение от popov654 (Сообщение 124813)
2) Сделать передачу методом POST всех строк сразу (либо в формате JSON, либо "склеив" их вручную). Это должно дать существенный выигрыш в скорости + избавить мои бедные логи от замусоривания бесконечными запросами к одному и тому же PHP-скрипту.

звучит хорошо !) посмотрим,как это на деле будет)

popov654 06.09.2011 04:50

:D
У меня вначале POST использовался) Только передавалась всегда вся форма, что было весьма нерационально. Плюс скрипт записи был довольно сложный. Теперешний способ даёт крайне хорошие результаты при маленьких изменениях (1-29 запросов к БД), но совершенно чудовищные показатели при запросах больших, когда требуется проапдейтить хотя бы полтаблицы (и это при том, что я ВСЮ таблицу сократил в два раза!)
Только что проверял такую масштабную модификацию, это адова жесть. 20-25 секунд ожидания даже на локалке (!)
Я уже не говорю про бедные лог-файлы Апача - ничего себе 580 запросов за одно редактирование :)
Там кстати после всей этой радости ещё запускается маленький cleanup таблицы для совместимости со старыми версиями (удалить записи-"пустышки"). В итоге около 30 секунд приходится ждать до завершения. При сохранении при помощи кнопки снизу один раз браузер и вовсе завис в состоянии загрузки, уже после алерта с успешным завершением :(

Так что, видимо, буду переделывать.

Однако, смущает проблема с индикацией: если я сделаю POST, невозможно будет отследить прогресс запроса. Или всё-таки есть способ это сделать?

ОлегА 06.09.2011 08:11

Вам видимо доставляет большое удовольствие писать такие большие сообщения )) мне аж читать лень... :)

ваый 06.09.2011 09:11

1. Вообще это должно делаться методом POST. Уже потому что для сохранения данных этот метод и предназначен. POST и PUT. Метод GET предназначен для получения данных. Что мешает передавать на сервер не всю форму, а лишь измененные записи? Id: value.

2. Это должно происходить одним запросом, а не каждый запрос на одну запись. Это настолько нелепо и ресурсоемко, как сделано сейчас, что я не понимаю даже, как ты еще сам не догадался, что что-то в твоем методе не то.

3. Использовать AJAX, а не image.src. В принципе, это автоматом, если перепишешь на POST.

4.
Цитата:

Сообщение от popov654
если я сделаю POST, невозможно будет отследить прогресс запроса

А это нафиг и не нужно, понимаешь? Ты не файл загружаешь, чтобы показывать прогресс. Ты просто выполняешь запрос, который будет отрабатывать за доли секунды (ну если сделаешь нормально, а не как сейчас, когда за 25 секунд), зачем тут прогресс бар?

popov654 07.09.2011 00:16

Да вот как видите, пока не сделал, не догадался. :( Кто же мог подумать, что это так долго будет происходить?
Хотя в принципе этого и следовало ожидать, 500-1000 запросов, 40 миллисекунд таймаут... Получаем 20-40 секунд времени на выполнение.
Из плюсов - ну во-первых всю форму не грузить когда пару оценок поменяли. Это вроде объективно.
Плюс код серверного скрипта, который вносит данные в базу, сократился до 8 строк (10 с небольшим с учётом всяких cleanup-ов). Было раза в 3 больше кода, кроме того, тот код был очень плохой (из-за плохой сопровождаемости, например он был чувствителен к ширине таблицы, которая задавалась в нём жёстко).
Ещё что было плохо - ну например, куча лишних записей в базе, не несущих никакой смысловой нагрузки, т.е. пустых, записываемых, что называется, "для симметрии".

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

Ну если время сократится так значительно, можно разумеется и без индикации обойтись. Впрочем, в случае загрузки файла я тоже не знаю как индикацию делать... Вот ВКонтакте это сделали - интересно, как)

popov654 07.09.2011 01:57

Кстати, я сделал индикатор прогресса. Работает красиво, но код пока не очень хороший, поскольку я что-то налажал с получением координат элементов и вынужден был прописать числовые значения, что считается плохой практикой. Впрочем, сойдёт пока, всё равно от этого отказаться придётся потом :)

popov654 07.09.2011 07:32

Старая версия будет доступна пока по адресу http://popov654.pp.ru/livemarks_old/login.html

popov654 07.09.2011 20:25

Походу какой-то странный облом с методом POST... Какая-то странная ошибка:

[07.09.2011 20:24:38] JavaScript - http://popov654.pp.ru/livemarks/edit.php
Event thread: click
Uncaught exception: DOMException: INVALID_STATE_ERR
Error thrown at line 444, column 5 in saveChanges(callback) in http://popov654.pp.ru/livemarks/edit.php:
req.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
called from line 429, column 9 in <anonymous function: document.form1.save.onclick>() in http://popov654.pp.ru/livemarks/edit.php:
saveChanges(function() {})


Ну и почему?.. Всё ведь правильно сделал :(

popov654 08.09.2011 01:28

Убрал эту строчку, это дало возможность отладить PHP-скрипт. Но без неё скрипт не видит данные :cray:

popov654 08.09.2011 01:30

А с ней опять ошибка та же самая...

popov654 08.09.2011 01:40

А, блин. Почитал, разобрался. Следовало сначала метод open() вызвать у XMLHttpRequest, а потом уже заголовок ставить :D

popov654 08.09.2011 02:20

Да копать его колотить... :-E

Теперь русские буквы приходят в битом виде. Причём они ещё в БД в таком виде заносятся. Самое забавное, кодирование с помощью encodeURIComponent() ровным счётом ничего не даёт. На стороне сервера всё раскодируется само, даже не нужно вызывать urldecode(), а вот в базу всё равно пишется чёрт знает как.

popov654 08.09.2011 02:28

Фуууф...
Вот эти 3 строки в конфиге были причиной всех несчастий:

Код:

mysql_query("SET character_set_client='cp1251'");
mysql_query("SET character_set_connection='cp1251'");
mysql_query("SET character_set_results='cp1251'");

Это всё потому, что у меня база в UTF-8 (привычка ещё со времён Ayola), а сайты в CP-1251. Но тут JavaScript похоже как раз в Юникоде отдаёт данные :)
Чтобы не рисковать остальными модулями, добавил сразу после include "config.php" следующие 2 строчки:

Код:

mysql_query("SET character_set_client='utf8'");
mysql_query("SET character_set_connection='utf8'");

И всё заработало (по крайней мере после того, как добавил верхнюю).

Добавлено спустя 2 минуты: кстати, нижняя, как оказалось, вообще не нужна. Что вполне логично: она отвечает за передачу данных между PHP и MySQL

popov654 08.09.2011 02:32

Кстати, этот инклюд попортил мне ещё массу нервов, когда переменная с безобидным именем $base (номер предмета) вдруг начала чудесным образом менять значение с 3 на livemarks. И что бы вы думали? Правильно, она использовалась в config.php для хранения имени базы данных, а я имел неосторожность произвести чтение значения из $_POST ДО инклюда :lol:

popov654 08.09.2011 15:00

УРАААА! Вышла версия 4.3 с новыми улучшенными алгоритмами сохранения данных! Подробнее тут: LiveMarks 4.3


Часовой пояс GMT +3, время: 11:19.