Ответы пользователя по тегу JavaScript
  • Как выбросить ошибку из промиса?

    bingo347
    @bingo347 Куратор тега JavaScript
    Бородатый программер
    Ваша главная проблема в говнокоде, в котором Вы сами не в состоянии разобраться.

    Для начала я отрефакторю вот этот кусок:
    const minSalary = Math.min(...employeeData.map(({ salary }) => salary));
    minSalaryEmployee = employeeData.filter(({ salary }) => {
        return salary === minSalary;
    });
    const [id, oldSalary] = [
        minSalaryEmployee[0].id,
        minSalaryEmployee[0].salary,
    ];
    const newSalary = oldSalary + (oldSalary / 100) * 20;
    return { id: id, salary: newSalary };
    ибо тут твориться полная жесть...
    Если привести в более читабельный вид:
    const minSalary = Math.min(...employeeData.map(({salary}) => salary));
    const minSalaryEmployee = employeeData.find(({salary}) => salary === minSalary);
    const {id, salary: oldSalary} = minSalaryEmployee;
    const newSalary = oldSalary + (oldSalary / 100) * 20;
    return {id, salary: newSalary};
    то сразу можно увидеть простор для оптимизации поиска минимума:
    const [minSalaryEmployee] = employeeData.reduce(([minEmployee, minSalary], employee) => {
        const {salary} = employee;
        return (salary < minSalary
            ? [employee, salary]
            : [minEmployee, minSalary]
        );
    }, [null, Infinity]);
    А заодно и формулу
    const newSalary = oldSalary + (oldSalary / 100) * 20;
    применив алгебру за 5 класс можно упростить доconst newSalary = oldSalary * 1.2;

    Следующим шагом стоит развернуть все промисы. Вообще вкладывать промисы в друг друга не очень хорошая идея. Наличие .then внутри колбэка другого .then или колбэка-раннера new Promise - воняет очень скверно.

    Немного поколдовав, заодно исправив ошибки с колбэками .then без return, получаем более читабельный, а главное работающий код:
    function increaseSalary() {
        return api.getEmployees()
            .then(employeeData => {
                const [minSalaryEmployee] = employeeData.reduce(([minEmployee, minSalary], employee) => {
                    const {salary} = employee;
                    return (salary < minSalary
                        ? [employee, salary]
                        : [minEmployee, minSalary]
                    );
                }, [null, Infinity]);
                const {id, salary: oldSalary} = minSalaryEmployee;
                const newSalary = oldSalary * 1.2;
                return {id, salary: newSalary};
            })
            .then(({id, salary}) => api.setEmployeeSalary(id, salary))
            .then(({name, id, salary}) => api.notifyEmployee(id, `Hello, ${name}! Congratulations, your new salary is ${salary}!`))
            .catch(e => api.notifyAdmin(e));
    }


    P.S. я бы еще бил все это добро на отдельные функции, ибо complexity 10 это все еще много...

    import api from 'path/to/api';
    
    export function increaseSalary() {
        return api.getEmployees()
            .then(findEmployeeWithMinSalary)
            .then(calculateNewSalary)
            .then(setEmployeeSalary)
            .then(notifyEmployee)
            .catch(notifyAdmin);
    }
    
    function findEmployeeWithMinSalary(employeeData) {
        const [minSalaryEmployee] = employeeData.reduce(minSalaryEmployeeReducer, [null, Infinity]);
        return minSalaryEmployee;
    }
    
    // Complexity is 3 - это самая сложная функция
    function minSalaryEmployeeReducer([minEmployee, minSalary], employee) {
        const {salary} = employee;
        return (salary < minSalary
            ? [employee, salary]
            : [minEmployee, minSalary]
        );
    }
    
    function calculateNewSalary({id, salary}) {
        return {
            id,
            salary: salary * 1.2
        };
    }
    
    function setEmployeeSalary({id, salary}) {
        return api.setEmployeeSalary(id, salary);
    }
    
    function notifyEmployee({name, id, salary}) {
        return api.notifyEmployee(id, `Hello, ${name}! Congratulations, your new salary is ${salary}!`);
    }
    
    function notifyAdmin(e) {
        return api.notifyAdmin(e);
    }
    Ответ написан
  • Добавлять/убирать класс у элементов при скролле?

    bingo347
    @bingo347 Куратор тега JavaScript
    Бородатый программер
    Вопрос поднимался здесь уже много раз...

    1. Оптимизировать можно с помощью IntersectionObserver
    2. Просто убирайте класс со всех остальных элементов
    Ответ написан
  • Как обернуть данные из колбэков в массив?

    bingo347
    @bingo347 Куратор тега JavaScript
    Бородатый программер
    Решение 0xD34F имеет сложность O(n2) из-за использования метода every
    Если это тестовое задание, могут и придраться, так как обычный счетчик позволяет решить со сложностью O(n)
    function getUsersInfo(ids, callback) {
      const {length} = ids;
      const results = Array(length);
      let doneCount = 0;
      ids.forEach((id, i) => {
        getUserInfo(id, user => {
          results[i] = user;
          doneCount++;
          if(doneCount === length) {
            callback(result);
          }
        });
      });
    }

    Ну и замечу, что Promise.all в v8 под капотом использует именно такой подход (разве что подсчет ведут от length к 0):
    https://github.com/v8/v8/blob/master/src/builtins/...
    Ответ написан
  • Одно ли и то же эти куски кода?

    bingo347
    @bingo347 Куратор тега JavaScript
    Бородатый программер
    1 - function expression
    Функция будет анонимная (без имени), что затруднит отладку, а так же не позволит делать рекурсивные вызовы.
    var всплывет наверх функции в которой была объявлена (оборачивающей), но до присваивания будет иметь значение undefined, вызов приведет к ошибке.
    Данный тип функций вообще не должен использоваться в нормальном коде.
    Функцию можно переопределить в последствии

    2 - named function expression
    Похоже на предыдущее, но имеет имя, что упрощает отладку и позволяет делать рекурсивные вызовы. В остальном все тоже самое.
    Вот так в переменную опять же присваивать не стоит, для дебага хватит стрелочной функции присвоенной в константу (в дебаггере примет имя константы). Но рационально использовать для колбэков, где нужна рекурсия или опять же для удобного дебага.

    3. function declaration
    Всегда должна иметь имя. Всплывает на верх (можно вызывать до объявления). Нельзя переопределить внутри той же функции (всегда будет вариант, тот что объявлен ниже).

    Добавлю еще 4 - arrow function expressionconst func = () => {};на сегодня лучше использовать его вместо function expression
    Ответ написан
  • Как при рекурсивном переборе древовидных объектов вызывать fetch на каждый item и на выходе получать измененный массив?

    bingo347
    @bingo347 Куратор тега JavaScript
    Бородатый программер
    const addingOptions = arr => Promise.all(arr.map(
        item => Promise.all([
            fetchOptions(item.id),
            item.children.length ? addingOptions(item.children) : []
        ]).then(([options, children]) => ({
            ...item,
            options,
            children
        }))
    ));
    Ответ написан
  • Как реализовать такое?

    bingo347
    @bingo347 Куратор тега JavaScript
    Бородатый программер
    Для начала наш бэк должен уметь отвечать на запрос вида "дай мне N товаров после товара X", иначе такая кнопка в принципе не имеет смысл.
    Так вот, при нажатии на кнопку "Показать больше" мы первым делом должны ее заблокировать (чтоб пользователь не на тыкал лишнего) и показать лоадер (вдруг инет пользователя медленный), а затем отправить наш запрос к бэку, чтоб он дал нам данные о дополнительных товарах.
    Когда данные загрузились, отрисовываем их в конец и в зависимости от того, есть ли у бэка еще товары (это он тоже должен нам сказать) мы или разблокируем или прячем кнопку "Показать больше".
    Все.
    Ответ написан
  • Как найти и заменить значение вложенного объекта?

    bingo347
    @bingo347 Куратор тега JavaScript
    Бородатый программер
    function cloneWithReplace(
        // клонируемый объект
        obj,
        // объект с функциями вида value => newValue, в соответствующих ключах для замены
        // нужен не всегда, поэтому по умолчанию сделаем пустой объект
        replacers = {},
        // Map с значениями, которые уже склонировали, дабы не попасть в рекурсию
        // так как внешний код обычно его не будет передавать, зададим значение по умолчанию
        storeMap = new Map()
    ) {
        // для начала чекнем, что объект уже клонировали:
        if(storeMap.has(obj)) {
            return storeMap.get(obj);
        }
    
        // получим тип объекта, он нам пригодится пару раз, дабы отличать функции
        const type = typeof obj;
    
        // если obj примитив, то его можно просто вернуть
        if(obj === null || (type !== 'object' && type !== 'function')) {
            return obj;
        }
    
        // создадим переменную с результатом и инициируем ее в зависимости от типа оригинала
        let result;
        if(type === 'function') {
            // функцию можно "склонировать" лишь обернув
            result = function() {
                return obj.apply(this, arguments);
            };
            // неплохо бы, чтоб клон правильно сообщал имя функции и количество аргументов
            // но так как IE не ест такую магию, обернем в try-catch
            try {
                Object.defineProperties(result, {
                    name: Object.getOwnPropertyDescriptor(obj, 'name'),
                    length: Object.getOwnPropertyDescriptor(obj, 'length')
                });
            } catch {}
        } else if(Array.isArray(obj)) {
            // массивы клонируем рекурсивно, при помощи map
            result = obj.map(value => cloneWithReplace(value, replacers, storeMap));
            // так как нормальные массивы не содержат других полей, кроме числовых
            // можно сохранить клон в защиту от рекурсии и вернуть результат
            storeMap.set(obj, result);
            return result;
        } else {
            // для всех других объектов просто создадим новый объект и скопируем ему ссылку на прототип
            result = Object.setPrototypeOf({}, Object.getPrototypeOf(obj));
        }
    
        // сохраним клон в защиту от рекурсии
        storeMap.set(obj, result);
    
        // осталось склонировать поля с заменой тех случаев, где у нас есть replacer
        for(const key of Object.getOwnPropertyNames(obj).concat(Object.getOwnPropertySymbols(obj))) {
            if(typeof replacers[key] === 'function') {
                // если есть replacer используем его
                result[key] = replacers[key](obj[key]);
            } else {
                // иначе клонируем поле рекурсивно
                result[key] = cloneWithReplace(obj[key], replacers, storeMap);
            }
        }
    
        return result;
    }
    
    // используем так:
    const newData = cloneWithReplace(data, {
        value: () => 'new value'
    });
    Ответ написан
  • Как изменить текст в буфере перед вставкой?

    bingo347
    @bingo347 Куратор тега JavaScript
    Бородатый программер
    document.addEventListener('paste', pasteText);
    function pasteText(e) {
      e.preventDefault();
      var txt = (e.originalEvent || e).clipboardData.getData('text/plain');
      e.target.value = txt;
    };
    Ответ написан
  • Как сравнить дату с датой из объекта?

    bingo347
    @bingo347 Куратор тега JavaScript
    Бородатый программер
    Дату в объекте нужно распарсить в объект Date
    Сами даты приводятся операторами сравнения к числу (unix timestamp в милисекундах) и вполне себе сравниваются:
    const date = new Date('02-10-2005'); // это лучше вынести за цикл, что бы не делать много раз одно и то же
    for (card of json.CardInfo) {
       if(new Date(card.Date) < date) {
        console.log('true');
      } else{
        console.log('false');
      }
    }

    P.S. вообще конструкций вида if(...) { true } else { false } лучше избегать
    for (card of json.CardInfo) {
        console.log(String(new Date(card.Date) < date));
    }
    этот пример отработает абсолютно аналогично предыдущему, но читать кода тут меньше
    Ответ написан
  • Оцените реализацию класса Promise?

    bingo347
    @bingo347 Куратор тега JavaScript
    Бородатый программер
    Ну во-первых оно не работает. Давайте разбираться с конца:
    p.then(data => {
      console.log(data)
    }).catch(err => {
      console.error(err)
    })
    Вы прямо каждый раз catch втыкаете? Прямо после каждого then?
    // не работает, хотя более чем нормально
    p.then(() => {}).then(() => {});
    
    // встречается конечно реже, но не работает
    p.catch(() => {}).catch(() => {});
    
    // а еще можно так, но у Вас нельзя
    p.then(() => {}, () => {})
    
    // а вот так работает, но неправильно:
    p.then(() => {});
    p.then(() => {});
    Ну и instanceof тоже сломали...

    Давайте дальше разбираться, как думаете в каком порядке будут логи?
    const p = Promise.resolve();
    console.log(1);
    p.then(() => console.log(3));
    console.log(2);
    У вас же этот порядок нарушится, и кстати да, так тоже не работает:
    new myPromise(resolve => resolve()).then(() => console.log('???'));


    DRY?
    onResolve(value) {
        if (this.status === 'ready') { return }
        this.thenCallback(value)
        this.status = 'ready'
      }
    
      onReject(reason) {
        if (this.status === 'ready') { return }
        this.catchCallback(reason)
        this.status = 'ready'
      }
    и тут
    return {
          then(callback) {
            ctx.thenCallback = callback
            return {
              catch(callback) {
                ctx.catchCallback = callback
              }
            }
          },
          catch(callback) {
            ctx.catchCallback = callback
            return {
              then(callback) {
                ctx.thenCallback = callback
              }
            }
          }
        }
    ну и return из конструктора не очень хорошая идея
    Ответ написан
  • Как работает сборщик мусор с колбеками Promise?

    bingo347
    @bingo347 Куратор тега JavaScript
    Бородатый программер
    Сборщики мусора (далее GC) бывают разные, в том же v8 используется сразу 3 типа GC в зависимости от поколения объектов (упрощенно молодое, старое и сложные случаи), но в большинстве своем принцип работы сводится к просчету достижимости из некоторого корня в дереве объектов (например глобального объекта, но не только его). v8 не является исключением, и его GC содержит C++ api для создания таких корней. Из JS мы данным api можем воспользоваться лишь косвенно, через сущности языка предоставляемые либо самим v8 либо платформой (браузером, node.js, deno и т.д.)
    Чтоб было понятно давайте рассмотрим простой пример:
    const h = 'Hello world!';
    const n = 'nothing';
    setTimeout(() => {
      console.log(h);
    }, 1000);
    У нас есть строковые переменные h и n. Переменная n нигде больше не используется и ее память очистится при ближайшей работе GC. А вот переменная h оказалась в замыкании созданном стрелочной функцией. И хотя в JS мы не можем достучаться до h имея ссылку на эту функцию, сама функция все таки имеет ссылку на h, а значит h не может быть уничтожена пока не будет уничтожена сама функция. В терминах GC ссылка на h будет серой, то есть сама ссылка на h недоступна из корня напрямую, но сейчас мы проверяем объекты, которые на нее ссылаются и истина будет зависеть от них (подробнее можете погуглить "mark black white and gray memory in gc").
    Давайте посмотрим на саму стрелочную функцию, которая держит h в замыкании. Из кода видно, что мы ее передаем в функцию setTimeout, о которой известно, что это api предоставленное платформой (а значит вероятно какая-то часть написана на C++), а так же, что она асинхронная. Платформе реализующей setTimeout наша функция понадобится после асинхронного ожидания и никто платформе не сможет гарантировать, что во время этого ожидания не будет работы GC, поэтому ей ничего не остается, кроме как запросить у v8 создание нового корневого дерева объектов, в которое и будет положена ссылка на данную функцию.
    После же выполнения таймаута платформе больше не нужна наша функция, поэтому ссылка на нее будет удалена из дерева объектов. А так как других ссылок на функцию нет, и она больше не доступна ни из одного корня - GC удалит из памяти и функцию и строку связанную h, которая так же стала недоступна из корня.

    Посмотрим на пример из вопроса. У нас есть стрелочная функция, которая удерживает на себе инстанс компонента через this ссылку (так как стрелочные функции замыкают this). Саму функцию в памяти удерживает промис порожденный вызовом loader('url'), так как мы отдали её в метод then. Других ссылок на данную функцию нет, а значит и сама функция и ее замыкание (инстанс компонента) будут "жить" не менее "жизни" промиса.
    Скажем был отправлен запрос на сервер, но потом компонент в котором был объявлен промис и колбек был удален.
    И после удаления приходит ответ от сервера, и он выполнит колбек. Это значит что колбек остался в памяти со всеми переменными контекста
    Если других ссылок не осталось, то инстанс компонента будет удерживаться от очистки через промис.

    Теперь стоит разобраться с самим промисом. У него может быть 3 состояния - pending, resolved или rejected. После перехода в состояния resolved или rejected промис может выполнить сохраненные колбэки в ближайшем микротаске, а после он удалит на них ссылки из себя, в следствии чего, память удерживаемая замыканием колбэка может быть очищена (при отсутствии на нее других ссылок, достижимых из какого-либо корня).
    В состоянии pending промис может потенциально находится бесконечно долго, при этом ссылаясь на все колбэки переданные ему в методы then, catch или finally, а значит так же косвенно ссылаясь на их замыкания. И тут все зависит от того, кто ссылается на данный промис, и достижим ли он из корня. И да, промис вполне может быть удален из памяти так и не дождавшись своего завершения.
    интересное умозаключение
    Если Promise - это обещание, то в данном случае оно будет нарушено?


    В комментах к вопросу есть еще один интересный пример:
    function getSomething(){
      return new Promise((resolve, reject)=>{
        if(sys_condition){
           resolve();
        } 
      })
    }
    
    function testPromise(){
      let config = {....}
      getSomething().then(()=>{
         #use config
         goOn(...config)
      })
    }
    
    testPromise();
    У нас есть вызванная 1 раз функция testPromise, которая получает из функции getSomething промис, в который с помощью метода then сохраняет колбэк, удерживающий в замыкании переменную config. Сам промис она нигде не сохраняет, что здесь очень важно.
    В функции getSomething мы просто возвращаем промис созданный через его конструктор, который как мы уже знаем нигде больше не сохраняется. И на этом могло бы все и закончится, без вызова колбэка независимо ни от чего. Но конструктор промиса выполняет свой колбэк синхронно, а кроме того он передает в него 2 функции - resolve и reject, которые в своем замыкании ссылаются на только что созданный промис (а это уже 2 ссылки на него, хотя мы то его никуда не сохраняли). Переменная reject никак не используется, а значит спокойно может быть удалена после завершения колбэка. Переменная resolve просто вызывается как функция внутри условия, но более тоже никак не используется и никуда не сохраняется, а значит так же может быть удалена.
    В этом примере. если sys_condition = false и resolve не вызовется, это значит что создается утечка памяти
    Нет, утечки памяти не будет. Колбэк созданный в testPromise будет удален вместе с замыканием, так и не вызвавшись ни разу.
    Ответ написан
  • Как отсортировать объект по вложенному массиву?

    bingo347
    @bingo347 Куратор тега JavaScript
    Бородатый программер
    const newArray = arr.slice(); // так как sort мутирует массив, а нужен новый
    const isAnnaSkill = skill => skill.name === 'Anna';
    const getAnnaLevel = item => item.skills.find(isAnnaSkill)?.level;
    newArray.sort((a, b) => getAnnaLevel(b) - getAnnaLevel(a));
    Ответ написан
  • Как прокачать навыки функционального программирования?

    bingo347
    @bingo347 Куратор тега JavaScript
    Бородатый программер
    Я бы начал с изучения книги Луиса Атенсио "Функциональное программирование на ...
    Дальше можно изучить серию статей "Мышление в стиле Ramda" на хабре
    Ответ написан
  • Как посчитать количество повторений символов в строке?

    bingo347
    @bingo347 Куратор тега JavaScript
    Бородатый программер
    Воспользуйтесь для подсчета символов обычным объектом, где ключи - буквы, а значения - их количество, а уже потом сформируйте из него массив.
    Ответ написан
  • Как отфильтровать массив?

    bingo347
    @bingo347 Куратор тега JavaScript
    Бородатый программер
    То что у Вас в полях created и viewed вполне можно отдать в конструктор Date, а даты уже можно сравнивать между собой
    console.log(new Date("2020-05-22 16:24:16")); // работает

    Дальше, последний created, тут нужно уточнение:
    Если массив messages всегда упорядочен по полю created, как и в примере, то можно просто брать последний элемент массива и работать с ним:
    const lastMessage = messages[messages.length - 1];
    console.log(lastMessage);

    Если такой гарантии нет, то нужно искать максимум:
    const lastMessage = messages.reduce(([maxDate, maxMessage], currentMassage) => {
      const currentDate = new Date(currentMassage.created);
      if(currentDate > maxDate) {
        return [currentDate, currentMassage];
      }
      return [maxDate, maxMessage];
    }, [-Infinity, null])[1];
    console.log(lastMessage);

    Ну а если с этим разберетесь, то с последним пунктом у Вас уже не должно быть проблем, тут все аналогично
    Ответ написан
  • Map это обьект или массив?

    bingo347
    @bingo347 Куратор тега JavaScript
    Бородатый программер
    В js все объект, что не является примитивом (простые объекты, экземпляры классов, массивы, Map, Set, функции).
    Так что да, Map это объект.
    А вот массивом он не является, ибоArray.isArray(new Map()) // false
    Здесь есть вот такая штука [[Entries]]. А entries возвращает массив свойств объекта
    Вообще-то entries возвращает итератор. Итератор - это тоже не массив.
    Ответ написан
  • Полноэкранный режим на сайте?

    bingo347
    @bingo347 Куратор тега JavaScript
    Бородатый программер
    Проблема в том, что при переходе по ссылке документ, на котором вызван фулскрин, перестает существовать, а вместо него формируется новый, и при этом происходит выход из фулскрина.
    Решения тут я вижу два:
    1. Костыльное. Кроме документа фулскринить еще можно video и iframe, и iframe может тут помочь, так как он останется неизменным, сколько бы страниц в него не загружалось.
    2. Сложное. Можно переделать сайт в SPA, тогда настоящих переходов не будет, и все страницы будут работать в пространстве одного документа
    Ответ написан