Ответы пользователя по тегу JavaScript
  • Почему response.json( ) после fetch асинхронен?

    sergiks
    @sergiks Куратор тега JavaScript
    ♬♬
    Посмотрите, как выглядит «текстовый чат» по протоколу HTTP, когда клиент запрашивает у сервера что-то, и получает ответ.

    Примерная очерёдность:
    Код на клиенте вызывает fetch(). Устанавливается соединение, передаётся запрос «дай /api/method» и передаются заголовки, типа «готов принять json».

    Сервер обрабатывает, думает и «пишет в чат»:
    1) Строку со статусом HTTP/1.1 200 OK – мол, норм, ща всё будет.
    2) Несколько строк HTTP-заголовков ответа: размер, тип и пр.
    Пустую строку – мол, заголовки всё.

    Вот в этот момент fetch уже понимает, как у него дела с этим запросом. И ресолвит первый промис. Уже ясно, что вроде всё ок, ща польют данные, и это надолго. Дальше вступает в работу объект Response.

    3) После пустой строки после заголовков ответа, сервер начинает лить данные тела ответа. Может пару букв, а может гигабайт дампа. Представьте медленный интернет, зарезанную скорость «недружественного» сайта и т.п.

    Данные медленно ползут... И наконец, полностью переданы-получены.

    Вот тут у объекта Response ресолвится его .json() промис. Посмотрите по ссылке — у того же объекта (мы его получаем после первого ресолва fetch()) есть и свойства, доступные сразу же, синхронно: например, объект с заголовками: свойство headers. Или свойство ok, значение которого следует из самой первой строки ответа сервера.

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

    sergiks
    @sergiks Куратор тега JavaScript
    ♬♬
    Перечитайте про стрелочные функции

    У стрелочных нет своего this.

    На момент создания функции logger() в вашем примере, this === window.
    И так и останется, как ни вызывай её.
    Ответ написан
    Комментировать
  • Почему выходит undefined?

    sergiks
    @sergiks Куратор тега JavaScript
    ♬♬
    1. randNum содержит число.
    2. questFunc() возвращает строку с текстом вопроса.
    3. people[`${questFunc()}`] это, например, people["Это девушка?"] – будет undefined.
      Массив people не имеет строкового ключа. В нём два элемента с индексами 0 и 1.

    Я бы предложил не дублировать текст вопросов, а работать с их индексами. Так лаконичнее:
    spoiler
    const questions = [
      'Это самец?',
      'Это девушка?',
      'Носит очки?',
      'Занимается спортом?',
      'У этого человека смуглая кожа?',
    ];
    const people = [
      { name: 'Егор', profile: [1, 0, 1, 1, 1] },
      { name: 'Залина', profile: [0, 1, 1, 1, 0] },
    ];
    
    const questionElement = document.querySelector('.question');
    
    let questionIndex; // индекс текущего вопроса
    function askQustion() {
      questionIndex = Math.floor(Math.random() * questions.length);
      questionElement.innerHTML = questions[questionIndex];
      return questions[questionIndex];
    }
    
    askQustion();
    
    // TODO: принять ответ пользователя (true/false)
    const answer = true; // допустим, ответил "да"
    
    const candidates = people
      .filter(({ profile }) => !!profile[questionIndex] === answer)
      .map(({ name }) => name)
      .join(', ');
    
    console.log('Кандидаты:', candidates);
    Ответ написан
    Комментировать
  • Как отследить дозагрузку страницы?

    sergiks
    @sergiks Куратор тега JavaScript
    ♬♬
    Использовать MutationObserver. Он может «наблюдать» за всем документом или какой-то его частью, и реагировать на изменения в дереве.
    Вот простой пример, где по таймеру добавляются div'ы в #container, а MutationObserver это замечает и пишет в #log:
    Ответ написан
    Комментировать
  • Как в данном случае составить регулярное выражение?

    sergiks
    @sergiks Куратор тега JavaScript
    ♬♬
    Если дело в браузере, где доступен DOM API, можно так:
    const tr = document.createElement('tr');
    tr.innerHTML = '<td>Продукт</td><td>24</td><td>A</td>' 
    tr.querySelector('td:nth-child(2)').textContent // "24"
    Ответ написан
    5 комментариев
  • Как удалить текст в ячейке после символа?

    sergiks
    @sergiks Куратор тега JavaScript
    ♬♬
    Вот, надеюсь, понятный способ:
    const nodes = [ ...document.querySelector('td').childNodes ];
    const index = nodes.findIndex(({ nodeName }) => nodeName === 'BR');
    if (index > -1) nodes[index + 1].remove();
    Находим все узлы DOM внутри td. Их будет 3:
    1. текстовый
    2. элемент BR
    3. и опять текстовый

    Следующий узел после найденного BR — удаляем.
    Ответ написан
    Комментировать
  • Почему не получается сортировка по array в консоли JS?

    sergiks
    @sergiks Куратор тега JavaScript
    ♬♬
    Метод sort() изменяет сам массив.
    Когда вызываете функцию myFunction() без второго аргумента, возвращается массив как есть. Как отсортировали его предыдущим вызовом.

    Лучше делать копию массива, и сортировать-возвращать уже её:
    function myFunction(arr, str) {
      const sortedArray = arr.slice(); // копия, чтобы не менять оригинал
    
      if (str === 'asc') {
        sortedArray.sort((a, b) => a - b);
      } else if (str === 'dsc') {
        sortedArray.sort((a, b) => b - a);
      }
    
      return sortedArray;
    }
    
    console.log(myFunction([1, 4, 3, 2], 'asc')); // [ 1, 2, 3, 4 ]
    console.log(myFunction([1, 4, 3, 2], 'dsc')); // [ 4, 3, 2, 1 ]
    console.log(myFunction([1, 4, 3, 2])); // [ 1, 4, 3, 2 ] без изменений
    Ответ написан
  • Как использовать fetchTickers с параметром в CCXT?

    sergiks
    @sergiks Куратор тега JavaScript
    ♬♬
    В браузере вот, каждые 5 сек обновляется курс с одной из бирж:

    Не нужно передать никаких params. Из ответа вытаскивать свойство last (не знаю, откуда взялось last_price)

    Документация публичного API
    Документация JS-библиотеки.
    Ответ написан
    1 комментарий
  • Как убрать data из документа base64?

    sergiks
    @sergiks Куратор тега JavaScript
    ♬♬
    Точно только PDF? Тогда просто по длине отрезать:
    reader.result.substring(28);

    Чуть более универсально разбить по запятой:
    const base64data = 'data:application/pdf;base64,ABCFIFIFI1212010'
      .split(',').pop();
    Ответ написан
    Комментировать
  • React Native: Как измененить стиль по клику?

    sergiks
    @sergiks Куратор тега JavaScript
    ♬♬
    завести состояние этого блока isDropped (true / false),
    по клику менять это состояние.
    В отрисовке блока стиль делать зависимым от состояния:
    { top: `${isDropped ? 48 : 0}px` }
    Ответ написан
    4 комментария
  • Как вывести изображение после загрузки?

    sergiks
    @sergiks Куратор тега JavaScript
    ♬♬
    вот пример:
    function handleFiles(files) {
      for (let i = 0; i < files.length; i++) {
        const file = files[i];
    
        if (!file.type.startsWith('image/')){ continue }
    
        const img = document.createElement("img");
        img.classList.add("obj");
        img.file = file;
        preview.appendChild(img); // Assuming that "preview" is the div output where the content will be displayed.
    
        const reader = new FileReader();
        reader.onload = (e) => { img.src = e.target.result; };
        reader.readAsDataURL(file);
      }
    }
    Ответ написан
  • Почему пишет дату NaN Invalid Date NaN, в NaN:NaN?

    sergiks
    @sergiks Куратор тега JavaScript
    ♬♬
    Попробуйте так.. Вслепую чуть подправил
    spoiler
    const oo = n => n.toString().padStart(2, '0');
    
    const formatDate = date => {
      const d = new Date(date);
      const month = oo(d.getMonth() + 1);
      const day = oo(d.getDate());
      const year = d.getFullYear();
    
      return [year, month, day].join('-');
    };
    
    const onMessage = (data, cred) => {
      const { time_mark: timeMark, id, nick, content } = data;
      const { nick: credNick } = cred;
      const msgDate = formatDate(timeMark);
      const currentDate = formatDate(new Date());
      const date = new Date(timeMark); // 2009-11-10
      const time = [date.getHours(), date.getMinutes()].map(oo).join(':');
    
      let Strtime;
      if (msgDate === currentDate) {
        Strtime = 'Сегодня в ' + time;
      } else {
        const month = date.toLocaleString('default', { month: 'long' });
        Strtime = `${oo(date.getDay())} ${month} ${date.getFullYear()}, в ${time}`;
      }
    
      $('#msg-container').append(`
          <div class="msg-box" id='${id}' class="${nick === credNick ? '' : 'not-my'}">
              <p class="author">${nick}</p>
              <p class="msg-text">${content}</p>
              <span class="time">${Strtime}</span>
          </div>
      `);
    };
    
    $(document).ready(() => {
      check();
      const socket = io();
      const cred = JSON.parse(localStorage.getItem('userData'));
      $.ajax({
        url: '/getmessage',
        type: 'POST',
        success({ status, msg, data }) {
          if (status === 'err') {
            return new Notification('Ошибка', { body: msg });
          }
          data.forEach(message => onMessage(message, cred));
          $('#all_mess').scrollTop($('#msg-container').height());
        },
      });
    
      socket.emit('hello', { nick: cred.nick });
    
      $('#messForm').submit(e => {
        e.preventDefault();
        const msg = $('#message').val();
        if (msg) {
          socket.emit('sendMess', { token: localStorage.getItem('token'), msg });
          $('#message').val('');
        }
      });
    
      socket.on('addMess', data => {
        onMessage(data, cred);
        $('#all_mess').scrollTop($('#msg-container').height());
      });
    
      socket.on('toLogin', () => {
        window.location.href = '/auth';
      });
    
      socket.on('helloMess', str => {
        $('#msg-container').append(`<span class="hello">${str.mess}</span>`);
        $('#all_mess').scrollTop($('#msg-container').height());
      });
    });
    
    function hideEl() {
      $('.hello').hide();
    }
    Ответ написан
    1 комментарий
  • Как вывести значение ключа объекта?

    sergiks
    @sergiks Куратор тега JavaScript
    ♬♬
    users
      .filter(({ balance }) => +balance.replace(/[$,]/g, '') < 2000)
      .map(({ phone }) => phone);
    // [ "+1 (885) 559-3422", "+1 (837) 586-3283" ]
    Ответ написан
    6 комментариев
  • Как лаконично добавить в объект пару ключ-значение при условии?

    sergiks
    @sergiks Куратор тега JavaScript
    ♬♬
    Можно так укоротить, что станет длиннее:
    this.selectedNodesInfo = selectedItems.map(({ id, IADDRESS_LEVELID, [fieldName]: name }) => {
      const result = { name: name ?? id, id };
      if (this.isAddress) {
        result.IADDRESS_LEVELID = IADDRESS_LEVELID;
      }
    
      return result;
    });

    Ну или так, ещё менее читабельно:
    this.selectedNodesInfo = selectedItems.map(({ id, IADDRESS_LEVELID, [fieldName]: name }) => ({
      name: name ?? id,
      id,
      ...(this.isAddress ? { IADDRESS_LEVELID } : null),
    }));
    Ответ написан
    Комментировать
  • Как обернуть первый символ span'ом?

    sergiks
    @sergiks Куратор тега JavaScript
    ♬♬
    const wrapFirstLetter = el => {
      const letter = el.textContent[0];
      el.innerHTML = `<span>${letter}</span>${el.textContent.substring(1)}`;
    }
    
    wrapFirstLetter(document.querySelector('h2'));


    Ответ написан
    2 комментария
  • Как добавить вариативность текста в код, в зависимости от UTM?

    sergiks
    @sergiks Куратор тега JavaScript
    ♬♬
    const params = (new URL(document.location)).searchParams;
    const utmCampaign = params.get('utm_campaign');
    
    const campaignTexts = {
      'vk_001': 'Привет, куплю ваших слонов в ВК!',
      'vk_002': 'Привет, куплю ваших слонов в группе!',
      'fb_001': 'Привет, куплю ваших слонов в Facebook!',
    };
    const text = encodeURIComponent(campaignTexts[utmCampaign] ?? 'Привет по умолчанию!');
    const randomUrl = `https://wa.me/${phone}?text=${text}`;
    Ответ написан
    1 комментарий
  • Как выставить шанс функции Math.random?

    sergiks
    @sergiks Куратор тега JavaScript
    ♬♬
    const phones = [
      { p: '9171857450', c: 1 },
      { p: '9880735438', c: 10 },
      { p: '9880735439', c: 100 },
      { p: '9880735779', c: 2 },
      { p: '9170997305', c: 2 },
      { p: '9170997493', c: 2 },
      { p: '9880634879', c: 5 },
      { p: '9170996154', c: 1 },
      { p: '9880728447', c: 1 },
    ];
    const chancesTotal = phones.reduce((acc, { c }) => acc + c, 0);
    const selected = Math.floor(Math.random() * chancesTotal);
    let phone;
    for (let i = 0, sum = 0; i < phones.length; i++) {
      sum += phones[i].c;
      if (selected < sum) {
        phone = phones[i].p;
        break;
      }
    }
    const randomUrl = `https://wa.me/${phone}?text=Привет!%20Пришлите%20мне%20цены%20на%20рыбку!`;
    Ответ написан
    5 комментариев
  • Как работает деструктуризация в этом примере?

    sergiks
    @sergiks Куратор тега JavaScript
    ♬♬
    Может, так понятнее. Примерно то же:
    const arr = [0, 1, 2, 3, 4, 5];
    const index = 3;
    [ ...arr.slice(0, index), 'Habr', ...arr.slice(index + 1) ]
    // [ 0, 1, 2, "Habr", 4, 5 ]
    Делается новый массив, в котором сначала идут элементы исходного от 0 до index (не включая index);
    затем вставляется некий новый элемент;
    затем добиваются элементы исходного начиная со следующего после index'ного.

    Фактически заменяется элемент под индексом index на некий новый. В отличие от метода splice(), которым можно было бы сделать тоже самое arr.splice(index, 1, 'Habr'), вариант в вопросе не изменяет исходный массив, а создаёт новый – что, в общем-то, «правильно».
    Ответ написан
    Комментировать
  • Как поменять местами элементы в списке стран?

    sergiks
    @sergiks Куратор тега JavaScript
    ♬♬
    У элементов, которые надо переставить, похоже, одинаковый принцип формирования id:
    "t-phonemask_" и двухбуквенный код страны.

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

    В исходной разметке эти элементы отсутствуют — подгружаются/формируются динамически. Поэтому запускать перестановку надо не сразу, а после формирования менюшки. Проще всего, но ненадёжно – через таймаут.
    const sortUp = () => {
      const parent = document.querySelector('div.t-input-phonemask__options-wrap > noindex');
      const goFirst = ['ru', 'ua', 'kz'];
      goFirst.reverse().forEach(cc => {
        parent.insertAdjacentElement('afterbegin', parent.querySelector('#t-phonemask_' + cc));
      });
    };
    setTimeout(sortUp, 1000);
    P.S. стоит Тильде чуть что-то поменять, изменить названия классов — перестанет работать.
    Ответ написан
  • Как ограничить количество совпадений одинаковых цифр в регулярном выражении?

    sergiks
    @sergiks Куратор тега JavaScript
    ♬♬
    Напоминалка
    Количество задаётся в фигурных скобках {n,m}.
    Например, /5{1,3}/ соответствует цифре 5 повторяющейся от 1 до 3 раз.
    Жадно. Т.е. если можно взять три – возьмёт три. Подробнее про квантификаторы (указатели количества).

    Решение
    ​const re = /(([1-68])\2{0,2}|([79])\3{0,3}|0)/g;
    
    ​"443777771999990005555551".match(re).join(', ')
    // "44, 3, 7777, 7, 1, 9999, 9, 0, 0, 0, 555, 555, 1"

    Расшифровка
    В круглых скобках через вертикальную черту – варианты:
    (A|B|C) == "A" ИЛИ "B" ИЛИ "C"

    В квадратных скобках наборы символов:
    [a-z] == любой один от "a" до "z"
    В данном случае:
    [1-68] == цифра от 1 до 6 или 8

    \2 или например \3 – отсылка к ранее захваченному в круглых скобках фрагменту
    /([79])\2{0,2}/ цифра 7 или 9. Сразу после неё – её же повтор от 0 до 2 раз. Т.е. в сумме это цифра 7 или 9, идущая до 3 раз подряд.
    Ответ написан
    6 комментариев