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

Ну а пока я застрял здесь и вынужден как-то скинуть фотки со своего телефона, у которого почему-то отвалился MTP. В работе у меня - страница фидбэка для полностью статического сайта и я подумал - О! А ведь там загрузчик файлов будет очень кстати, заодно и фотки скину. И как только я начал его делать, в одном из списков рассылки Devuan вижу сообщение: как достали эти грёбаные телефоны, как скинуть файлы помогите пожалуйста.

Ну, думаю, раз не я такой один, значит оно того стоит. Когда-то я делал подобное, давным-давно, ещё во времена jQuery, но тогда был какой-то готовый компонент. А сейчас я ничего стороннего не хотел. Полез в MDN. Осмысленно всё скопипастил, и вот докладываю о результатах. Кое-что, конечно, повычистил, например, отслеживание файлов с одинаковыми имёнами, если они из разных каталогов, показ миниатюр, но это исключительно ради простоты изложения. Такие мелочи вы и сами легко сделаете.

Итак, базовый модуль загрузчика на Javascript. Модулем его назвать можно с большой натяжкой, так как весь Javascript и CSS у меня собирается в один HTML файл, упаковывается в gz, и дальше nginx раздаёт его налево и направо максимально быстро. Внутри HTML модуль Javascript становится анонимным без возможности экспорта чего-либо, поэтому приходится использовать старые недобрые методы.

(() => {

class FileUploader
{
    constructor(settings)
    {
        const default_settings = {
            url: '/',
            chunk_size: 512 * 1024,  // последний chunk может быть в полтора раза больше,
                                     // не забываем про лимиты request body на сервере
                                     // (у NGINX по умолчанию 1M)
            file_name_header: 'File-Name'  // что-нибудь стандартное типа Content-Disposition
                                           // было бы лучше, но его сложнее парсить
        };

        this.settings = Object.assign({}, default_settings, settings);
        this.upload_queue = [];
    }

    upload(file, params)
    /*
     * Добавляем файл в очередь, и если загрузка ещё не в процессе - тогда начинаем.
     */
    {
        const start_upload = this.upload_queue.length == 0;

        // Создаём file_item и добавляем в начало очереди
        const file_item = new FileItem(this, file, params);
        this.upload_queue.push(file_item);

        if(start_upload) {
            // если вызываем асинхронную функцию без await, получим promise,
            // либо fulfilled, либо pending. Но он нам всё равно не нужен.
            this._async_upload_files().then();
        }
    }

    progress(file, params, chunk_start_percentage, chunk_end_percentage, percentage)
    /*
     * Этот метод вызывается для отображения прогресс-бара.
     * Реализуем его в производном классе.
     */
    {
    }

    async upload_complete(file, params)
    /*
     * Этот метод вызывается по завершении загрузки.
     * Реализуем его в производном классе.
     */
    {
    }

    async _async_upload_files()
    {
        // обрабатываем очередь загрузки
        while(this.upload_queue.length != 0) {
            await this.upload_queue[0].upload();
            this.upload_queue.shift();
        }
    }
}

class FileItem
/*
 * Элемент очереди загрузки.
 */
{
    constructor(uploader, file, params)
    {
        this.uploader = uploader;
        this.file = file;
        this.params = params;
    }

    async upload()
    {
        var chunk_start = 0;
        var chunk_size;
        while(chunk_start < this.file.size) {
            const remaining_size = this.file.size - chunk_start;

            // загружаем кусками default_chunk_size, последний кусок допускается
            // в полтора раза больше, чем default_chunk_size
            if(remaining_size < 1.5 * this.uploader.settings.chunk_size) {
                chunk_size = remaining_size;
            } else {
                chunk_size = this.uploader.settings.chunk_size;
            }

            const chunk = this.file.slice(chunk_start, chunk_start + chunk_size);
            // XXX сохранять (start, end) в слайсе - грязный хак, а что делать?
            chunk.start = chunk_start;
            chunk.end = chunk_start + chunk_size;
            while(true) {
                try {
                    await this._upload_chunk(chunk);
                    break;
                } catch(error) {
                    console.log(`${this.file.name} upload error, retry in 5 seconds`);
                    await new Promise(resolve => setTimeout(resolve, 5000));
                }
            }

            chunk_start += chunk_size;
        }
        await this.uploader.upload_complete(this.file, this.params);
    }

    _upload_chunk(chunk)
    {
        // Эта функция использует non-awaitable XMLHttpRequest, поэтому не может быть async.
        // Но мы вызываем её с await, так что должны вернуть promise.

        const self = this;

        return new Promise((resolve, reject) => {

            const reader = new FileReader();
            const xhr = new XMLHttpRequest();

            xhr.upload.addEventListener(
                "progress",
                (e) => {
                    if(e.lengthComputable) {
                        const percentage = Math.round((e.loaded * 100) / e.total);
                        self._update_progress(chunk, percentage);
                    }
                },
                false
            );

            xhr.onreadystatechange = () => {
                if(xhr.readyState === xhr.DONE) {
                    if(xhr.status === 200) {
                        self._update_progress(chunk, 100);
                        resolve(xhr.response);
                    } else {
                        reject({
                            status: xhr.status,
                            statusText: xhr.statusText
                        });
                    }
                }
            };

            xhr.onerror = () => {
                reject({
                    status: xhr.status,
                    statusText: xhr.statusText
                });
            };

            xhr.open('POST', this.uploader.settings.url);

            const content_range = `bytes ${chunk.start}-${chunk.end - 1}/${this.file.size}`;
            xhr.setRequestHeader("Content-Range", content_range);
            xhr.setRequestHeader("Content-Type", "application/octet-stream");
            xhr.setRequestHeader(this.uploader.settings.file_name_header, this.file.name);

            reader.onload = (e) => {
                xhr.send(e.target.result);
            };

            reader.readAsArrayBuffer(chunk);
            self._update_progress(chunk, 0);
        });
    }

    _update_progress(chunk, percentage)
    {
        // считаем проценты и вызываем метод progress
        const chunk_start_percentage = chunk.start * 100 / this.file.size;
        const chunk_end_percentage = chunk.end * 100 / this.file.size;
        const upload_percentage = chunk_start_percentage + chunk.size * percentage / this.file.size;
        this.uploader.progress(
            this.file,
            this.params,
            chunk_start_percentage.toFixed(2),
            chunk_end_percentage.toFixed(2),
            upload_percentage.toFixed(2)
        );
    }
}

// типа экспортируем FileUploader
window.FileUploader = FileUploader;

})();

HTML и остальной Javascript:

<h3>Upload Files</h3>
<p>
    <button id="file-select">Choose Files</button> or drag and drop to the table below
</p>
<table id="file-list">
    <thead>
        <tr><th>File name</th><th>Size</th></tr>
    </thead>
    <tbody>
    </tbody>
</table>
<template id="file-row">
    <tr><td></td><td></td></tr>
</template>
<input type="file" id="files-input" multiple style="display:none">

<script>
    const upload_complete_color = 'rgb(0,192,0,0.2)';
    const chunk_complete_color = 'rgb(0,255,0,0.1)';

    class Uploader extends FileUploader
    {
        constructor()
        {
            super({url: '/api/feedback/upload'});

            this.elem = {
                file_select:  document.getElementById("file-select"),
                files_input:  document.getElementById("files-input"),
                file_list:    document.getElementById("file-list"),
                row_template: document.getElementById('file-row')
            };
            this.elem.tbody = this.elem.file_list.getElementsByTagName('tbody')[0];

            this.row_index = 0;

            this.set_event_handlers();
        }

        set_event_handlers()
        {
            const self = this;
            this.elem.file_select.addEventListener(
                "click",
                () => { self.elem.files_input.click(); },
                false
            );
            this.elem.files_input.addEventListener(
                "change",
                () => { self.handle_files(self.elem.files_input.files) },
                false
            );

            function consume_event(e)
            {
                e.stopPropagation();
                e.preventDefault();
            }

            function drop(e)
            {
                consume_event(e);
                self.handle_files(e.dataTransfer.files);
            }

            this.elem.file_list.addEventListener("dragenter", consume_event, false);
            this.elem.file_list.addEventListener("dragover", consume_event, false);
            this.elem.file_list.addEventListener("drop", drop, false);
        }

        progress(file, params, chunk_start_percentage, chunk_end_percentage, percentage)
        {
            params.progress_container.style.background = 'linear-gradient(to right, '
                + `${upload_complete_color} 0 ${percentage}%, `
                + `${chunk_complete_color} ${percentage}% ${chunk_end_percentage}%, `
                + `transparent ${chunk_end_percentage}%)`;
        }

        async upload_complete(file, params)
        {
            // красим зелёным всю строку
            params.progress_container.style.background = upload_complete_color;
            params.progress_container.nextSibling.style.background = upload_complete_color;
        }

        handle_files(files)
        /*
         * обрабатываем здесь файлы от drag'n'drop или диалога выбора
         */
        {
            for(const file of files) {
                const cols = this.append_file(file.size);
                this.upload(file, {progress_container: cols[0]});
            }
        }

        append_file(size)
        /*
         * Добавляем файл в таблицу, возвращаем список ячеек.
         */
        {
            const rows = this.elem.tbody.getElementsByTagName("tr");
            var row;
            if(this.row_index >= rows.length) {
                row = this.append_row();
            } else {
                row = rows[this.row_index];
            }
            this.row_index++;

            const cols = row.getElementsByTagName("td");
            cols[1].textContent = size.toString();
            return cols;
        }

        append_row()
        /*
         * Добавляем пустую строку к таблице.
         */
        {
            const tbody = this.elem.file_list.getElementsByTagName('tbody')[0];
            const row = this.elem.row_template.content.firstElementChild.cloneNode(true);
            tbody.appendChild(row);
            return row;
        }


    const uploader = new Uploader();

    // инициализируем таблицу - добавляем пять пустых строк
    for(let i = 0; i < 5; i++) uploader.append_row();

</script>

И, наконец, кусочек серверной части:

import os.path
import re
from starlette.responses import Response
import aiofiles.os

# Ничего этого в aiofiles нет. На момент написания, по крайней мере.
aiofiles.os.open = aiofiles.os.wrap(os.open)
aiofiles.os.close = aiofiles.os.wrap(os.close)
aiofiles.os.lseek = aiofiles.os.wrap(os.lseek)
aiofiles.os.write = aiofiles.os.wrap(os.write)

re_content_range = re.compile(r'bytes\s+(\d+)-(\d+)/(\d+)')

@expose(methods='POST')
async def upload(self, request):
    '''
    Ловим и записываем кусок файла.
    '''
    data = await request.body()
    filename = os.path.basename(request.headers['File-Name'])
    start, end, size = [int(n) for n in re_content_range.search(request.headers['Content-Range']).groups()]

    fd = await aiofiles.os.open(filename, os.O_CREAT | os.O_RDWR, mode=0o666)
    try:
        await aiofiles.os.lseek(fd, start, os.SEEK_SET)
        await aiofiles.os.write(fd, data)
    finally:
        await aiofiles.os.close(fd)
    return Response()

Что сказать в заключение? Мы бегаем по кругу. Процессоры мощнее, памяти больше, а загрузить все файлы за один раз мне не удалось. Браузер вылетал с OOM после 20-30 загруженных фоток, и это без показа миниатюр. Или я где-то накосячил?

Комментарии (60)


  1. Grey83
    22.07.2023 20:24
    +5

    Можно поставить телегу на обоих устройствах и перекидывать через неё.
    Если на компе винда, то можно поднять на ПК простенький файловый сервер (с удобными настройками прав доступа и прочими плюшками) с помощью HTTP Fle Server (я его юзаю как минимум с 2017-го).


    И в конце концов можно сделать на ПК шару через самбу и открыть её с помощью X-Plore с ведроида. Или поднять FTP-сервер и опять же зайти на него через X-Plore.
    При этом не нужен браузер. И вылетать после 30 фоток не должно.


    1. amateur80lvl Автор
      22.07.2023 20:24

      Чуваку в списке рассылки ещё более креативные способы предлагали. Своё облачное хранилище, например :) Но загрузчик мне и так пригодился бы, вот и решил совместить приятное с полезным.


      1. petropavel
        22.07.2023 20:24
        +1

        Вот не креативный способ для ленивых: https://play.google.com/store/apps/details?id=com.ghisler.tcplugins.wifitransfer

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


    1. Dart55
      22.07.2023 20:24

      Если на обоих устройствах есть интернет и браузер то file.pizza (peer-to-peer работает через WebRTC).


      1. Grey83
        22.07.2023 20:24

        А инет-то зачем?
        Он же не через внешние сервера должно работать?


        1. Dart55
          22.07.2023 20:24

          Сам сайт file.pizza со скриптами для передачи файлов откуда то надо загрузить. И да, для пользователей в одной локальной сети, трафик всё равно идёт через интернет, маршрутизатор провайдера. Удобство способа в том, что ненужно устанавливать дополнительных программ не на одно из устройств.


          1. Grey83
            22.07.2023 20:24

            для пользователей в одной локальной сети
            Тогда фигня это.
            Проще и быстрее торрентокачалки поставить на оба устройства и магнит кинуть.
            Они в локалке и без инета должны файлы отдавать, вроде.


            1. Dart55
              22.07.2023 20:24

              Способ хорош, когда нужно один раз обменяться файлами с людьми далёкими от IT которые не умеют в мессенджеры и соцсети, сайт легко запомнить "файл пицца".


              1. Grey83
                22.07.2023 20:24

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


    1. andrejbestuzhev
      22.07.2023 20:24
      +4

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

      Хотя когда-то были ИК-порты, но где это всё теперь?


      1. ri1wing
        22.07.2023 20:24
        -1

        Штатные средства?

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


        1. amateur80lvl Автор
          22.07.2023 20:24
          +2

          Всё это требует интернета. Самое штатное из всех средств должно быть USB, как наикратчайший путь.


          1. baldr
            22.07.2023 20:24
            +1

            Ну даже USB не все проблемы решает. Вот прислали мне на телефоне в Viber ссылку на Zoom-митинг, например, а я ее хочу открыть на десктопе..

            Либо сохраняй в файл и передавай его, либо ставь два одинаковых мессенджера на десктоп и телефон..


            1. PrinceKorwin
              22.07.2023 20:24

              Шаринг через почту? Клиенты есть везде. Не самое элегантное, но рабочее решение.


              1. amateur80lvl Автор
                22.07.2023 20:24

                Пользуюсь, и очень часто. Когда уж совсем прижмёт. Но с видосами не всегда работает из-за их дикого размера.


              1. baldr
                22.07.2023 20:24

                Ну да, вот так все и придумывают - каждый раз что-то отдельное.

                У жены iPhone и макбук, но даже там если ей нужно передать несколько картинок с телефона на ноут - то ей проще открыть Viber-чат со мной, напихать их туда, а потом скачать на ноуте.

                У меня на ноуте нет Viber, поэтому чтобы прислать ей ссылку - я копирую ее из браузера ноута в свой пустой канал-помойку в телеграме, потом на телефоне открываю телегу и оттуда копирую в Viber.

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


                1. amateur80lvl Автор
                  22.07.2023 20:24

                  Как-то давно смотрел видос, где Пайк рассказывал про свой go, и в конце он говорил что-то про идею сервиса офигенно лёгкого обмена файлами. Я уже забыл суть, но, похоже, идея так и осталась идеей. За это время другие чуваки успели придумать свои деньги :)

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

                  Но иногда за них огребают, увы.


                1. Akuma
                  22.07.2023 20:24

                  Что не так с передачей картинок эирдропом то?

                  Там как раз с файлами по несколько гб могут быть проблемы. А фоточки летают шустро.


          1. ri1wing
            22.07.2023 20:24
            -1

            Ну так извините, 2023 год на дворе. И я отвечал вот на это:

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

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


            1. amateur80lvl Автор
              22.07.2023 20:24
              +2

              в linux всё печально до сих пор, а виндовсом я пользовался последний раз в 2012 :(

              не знаю, кто вас заминусовал, но я это исправил, ибо нефиг

              А вообще, есть места, где интернета нет даже в 2023. Особенно актуально для тех, кто бороздит моря под парусом.


            1. andrejbestuzhev
              22.07.2023 20:24
              +1

              А если интернет недоступен, а два устройства лежат рядом? Одно на Винде/линупсе, другое на иосе/андройде. Это ведь не какой-то экзотический кейс.


              1. ri1wing
                22.07.2023 20:24
                -1

                Компьютер без интернета в 2023 году c моей точки зрения экзотический кейс.
                Но в крайнем случае всё равно существует USB кабель, просто он менее удобен, чем облако.

                Это у ТСа, насколько я понял, проблема, что у него передача по кабелю отвалилась, но его решение в отсутствие интернета так же работать не будет.


                1. amateur80lvl Автор
                  22.07.2023 20:24

                  есть локалка, так что работает и без


                  1. petropavel
                    22.07.2023 20:24
                    +1

                    вот-вот, локалки и должно быть достаточно. а где-то в поле можно wifi tethering сделать на одном устройстве и подключиться другим.

                    никогда не понимал людей, которые, если сосед просит одолжить дрель, вместо того, чтобы постучать в соседнюю дверь и отдать, отсылают её почтой в Орегон, чтобы откуда её переслали почтой же соседу.


                1. andrejbestuzhev
                  22.07.2023 20:24
                  +1

                  Компьютер без интернета в 2023 году c моей точки зрения экзотический кейс.

                  Полёт в европу с двумя пересадками - насколько экзотика? :)

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


                  1. ri1wing
                    22.07.2023 20:24

                    А вам прямо кровь из носу надо сидя в самолёте фотки синхронизировать и подождать это никак не может?

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

                    У меня роутер, к которому проводом подключен комп, и который раздаёт вай-фай для ноута и телефона. Мобильный интернет дома неудобен


                    1. andrejbestuzhev
                      22.07.2023 20:24
                      +1

                      Мне прям кровь из носу хочется иметь возможность передать файлы с мультиком с почти посаженного ноутбука на заряженный. Необязательно в самолёте, можно даже между пересадками.
                      > У меня роутер
                      Роутер на каждую встречу носите?

                      Мы вообще не о том, напомню тезис из оппоста:
                      >  И что самое смешное - передача файлов между девайсами до сих пор проблема. 

                      А вы меня пытаетесь убедить, что у меня либо повод не слишком веский, либо у вас wifi-роутер где-то рядом. Я в аэропорту, в другой стране, до стыковочного рейса 4 часа и мультики вот-вот закончатся вместе с батарейкой :D


                      1. ri1wing
                        22.07.2023 20:24

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

                        Если между ноутбуками, то флэшка / внешний жёсткий диск решают эту проблему.

                        Роутер на каждую встречу носите?

                        Нет. Зачем?

                        И что самое смешное - передача файлов между девайсами до сих пор проблема. 

                        Самое смешное, что это уже давно не проблема.

                        Я в аэропорту, в другой стране, до стыковочного рейса 4 часа и мультики вот-вот закончатся вместе с батарейкой 

                        А в аэропорту нигде-нигде нельзя найти бесплатную розетку чтобы подзарядиться?


                      1. andmerk93
                        22.07.2023 20:24

                        Смотреть мультики просто с флешки?


          1. Akuma
            22.07.2023 20:24

            Airdrop не требует интернета и работает между любыми эпл-устройствами.

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


            1. Grey83
              22.07.2023 20:24

              Для очень больших файлов — провода и флешки.
              «Очень большие» — это больше 10ГБ или больше 100ГБ?
              Потому что порядка 10ГБ я обычно через WIFi закидываю посредством X-Plore на ведроиде и шары через самбу на виндовом ПК.
              При этом файлы перекидываются в обоих направлениях именно со смарта.


              1. Akuma
                22.07.2023 20:24

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


      1. baldr
        22.07.2023 20:24
        +1

        Хотя когда-то были ИК-порты, но где это всё теперь?

        С учетом скорости ИК-порта солнце успеет совершить свой регулярный оборот вокруг земли пока эта моргалка скопирует современные фоточки на 15+МБ.


        1. andrejbestuzhev
          22.07.2023 20:24

          C учётом развития технологий, можно было бы придумать и стандартизировать что-нибудь за 20 лет.


        1. vladimirgamalian
          22.07.2023 20:24
          +1

          Вроде Li-Fi (https://ru.m.wikipedia.org/wiki/Li-Fi) потихоньку развивается и даже в десятки гигабит уже вроде как умеет.


  1. n2dt4qd2wg9b
    22.07.2023 20:24

    BabyFTP на винде и тотал коммандер на ведроиде.

    Бэкап всего телефона быстрее, чем через мер3кий MTP даже через USB3


    1. Grey83
      22.07.2023 20:24

      Бэкап всего телефона быстрее, чем через мер3кий MTP даже через USB3
      Ни разу не быстрее: USB3.0 согласно спецификациям имеет скорость до 5Гбит/с, что примерно совпадает со скоростью чтения с SSD через SATA3.0.
      В каком телефоне имеется полная скорость передачи данных стандарта ax (остальные развивают максимум ту же скорость, что и SATA3.0 (как ac) или же меньшую (все остальные))? При этом должно быть поддержка того же стандарта ax и у второго устройства.


      1. n2dt4qd2wg9b
        22.07.2023 20:24

        Вы мне как теоретик. Я вам - как практик.

        S23+ через USB3 кабель по MTP делает полную копию медленнее, чем через FTP и беспроводную сеть ac

        Машина с FTP подключена в проводную сеть.


  1. rootdefault
    22.07.2023 20:24

    Может я слишком быстро пролистал код, но где мы удостоверяемся что грузим медиа файл, а не какой-нибудь бэкдор? Ну разве что на права 0666 уповаем?


    1. amateur80lvl Автор
      22.07.2023 20:24
      +2

      А не пофиг ли? Мы же не собираемся загруженный файл выполнять.


  1. HemulGM
    22.07.2023 20:24
    -2

    Google Drive, Yandex Disc и куча других облачных сервисов. Все имеют клиенты на десктоп и мобилки. Даже делать ничего не надо, т.к. некоторые добавляют в контекстное меню пункт "Поделиться", который копирует файл для синхронизации. После чего на телефоне в клиенте он виден и может быть открыт.


  1. Lazytech
    22.07.2023 20:24
    +2

    Извиняюсь за занудство, но ES6 (он же ES2015) давным-давно поддерживается во всех мало-мальски современных веб-браузерах. Используя синтаксис ES2015+, можно сделать некоторые фрагменты кода чуть более простыми для восприятия.

    Например, фрагмент

    this.settings = Object.assign({}, default_settings, settings);
    

    можно заменить на

    this.settings = { ...default_settings, ...settings };
    

    javascript - Object spread vs. Object.assign - Stack Overflow


    1. amateur80lvl Автор
      22.07.2023 20:24

      Я что-то упускаю? Какая строчка попахивает мамонтом?


      1. Lazytech
        22.07.2023 20:24

        Запоздало заметил ваш ответ. Дополнил свой первый комментарий.

        P.S. Да и var нынче не в моде. Уже не помню, когда последний раз его использовал. Хотя, опять же, это всего лишь мое занудство.


      1. Lazytech
        22.07.2023 20:24
        +3

        В порядке занудства такжу отрефакторил этот фрагмент:

                {
                    const rows = this.elem.tbody.getElementsByTagName("tr");
                    var row;
                    if(this.row_index >= rows.length) {
                        row = this.append_row();
                    } else {
                        row = rows[this.row_index];
                    }
                    this.row_index++;
        
                    const cols = row.getElementsByTagName("td");
                    cols[1].textContent = size.toString();
                    return cols;
                }
        

        Вот что у меня получилось:

                {
                    const rows = this.elem.tbody.getElementsByTagName("tr");
                    const row = this.row_index < rows.length
                      ? rows[this.row_index];
                      : this.append_row()
                    this.row_index++;
        
                    const cols = row.getElementsByTagName("td");
                    cols[1].textContent = `${size}`;
                    return cols;
                }
        

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


        1. amateur80lvl Автор
          22.07.2023 20:24

          Понял, спасибо! Javascript у меня очень эпизодический, но я стараюсь не отставать.


        1. baldr
          22.07.2023 20:24
          +1

          Я больше по бэкенду, но позволю себе выразить свое мнение: тернарный оператор выглядит отвратительно во всех языках. Читаемость кода совсем не улучшается. Хотя сэкономило две строчки, да.


          1. Alexandroppolus
            22.07.2023 20:24
            +2

            Зато в данном кейсе тернарник позволил заменить var/let на const


          1. Lazytech
            22.07.2023 20:24
            +2

            О вкусах не спорят, есть тысяча мнений (с) Высоцкий

            К примеру, Python'овский тернарный оператор не фонтан, а JavaScript'овский, на мой взгляд, очень даже читабельный, особенно если разбить код на три строки с отступом в последних двух.

            Ternary operator: bad or good practice? - Stack Overflow

            Хотя, как известно, миллионы леммингов не могут ошибаться...

            P.S. Запоздало заметил у себя ошибку, а именно поставленную не в том месте точку с запятой:

                        const row = this.row_index < rows.length
                          ? rows[this.row_index];
                          : this.append_row()
            

            Исправляю:

                        const row = this.row_index < rows.length
                          ? rows[this.row_index]
                          : this.append_row();
            

            Я там поменял местами две строки ради того, чтобы заменить оператор >= на <. В общем, хотелось как лучше, а получилось как всегда. :)


          1. amateur80lvl Автор
            22.07.2023 20:24
            +1

            Мне понравилось, поскольку убирается нависший сверху var. Но вот в другом месте

            if(remaining_size < 1.5 * this.uploader.settings.chunk_size) {
                chunk_size = remaining_size;
            } else {
                chunk_size = this.uploader.settings.chunk_size;
            }

            я бы оставил if-else как есть


            1. Lazytech
              22.07.2023 20:24
              +1

              Не вижу проблем с использованием тернарного оператора и в этом случае.

                          const chunk_size = (remaining_size < 1.5 * this.uploader.settings.chunk_size)
                            ? remaining_size
                            : this.uploader.settings.chunk_size;
              

              Длина первой строки составляет 90 символов, что в некоторых случаях может быть неудобно или запрещено правилами (линтер и иже с ними). Но даже в таком случае можно выкрутиться, дополнительно повысив читабельность кода:

                          const size_upper_limit = 1.5 * this.uploader.settings.chunk_size;
                          const chunk_size = (remaining_size < size_upper_limit)
                            ? remaining_size
                            : this.uploader.settings.chunk_size;
              


              1. amateur80lvl Автор
                22.07.2023 20:24

                Здест скученность строк начинает напрягать. Назревает необходимость пустой строки между const


                1. Lazytech
                  22.07.2023 20:24

                  Возможно. Кстати, приведенный выше код можно еще немного улучшить:

                  			const is_below_limit = remaining_size < 1.5 * this.uploader.settings.chunk_size;
                  			const chunk_size = is_below_limit
                                ? remaining_size
                  			  : this.uploader.settings.chunk_size;
                  

                  Но, опять же, первая строка становится чересчур длинной.

                  Добавлю, что на практике использую традиционный JavaScript'овский camelCase, а не snake_case.


                  1. amateur80lvl Автор
                    22.07.2023 20:24

                    Я для себя пишу. Давным-давно читал какое-то исследование про оба стиля, и примкнул к сторонникам подчёркиваний. На работе, конечно, от коллектива отрываться нельзя, но для меня это сейчас неактуально.


  1. vk6677
    22.07.2023 20:24
    +3

    Использовал "KDE Connect" для передачи данных и управления через WiFi. К сожалению, тоже не без проблем решение.


    1. VadimProfii
      22.07.2023 20:24

      Именно, мой новый телефон работает только в одну сторону - на ПК с тела. Наоборот- ни в какую. А жаль- прога была хорошая...


  1. freelook00
    22.07.2023 20:24
    +1

    Вставлю свои пять копеек в вопрос обмена телефон-ПК: я использую CX Проводник, у него есть функция FTP сервера. Не по кабелю, конечно, но через общий wi-fi. Реально спасает.

    А так, да, через почту, телегу и т. д.


    У меня когда-то был КПК на Windows, который бэкапился и синхронизировался через приложение на ПК - очень этой фишки не хватает на современных Android-ах. iPhone, вроде, можно через iTunes бэкапнуть.


    1. Grey83
      22.07.2023 20:24

      Сейчас просто синхронизация через сервера где-то в США/Китае/Корее/etc.
      Раньше да, можно было бэкапить через приложение производителя устройства.
      То же OVI от Нокии. Хотя были и универсальные программы.


  1. SKProCH
    22.07.2023 20:24
    +4

    Если нужно постоянно держать синхронизированной какую-то папку с файлами, то можно воспользоваться Synthing, который позволяет быстро сделать то, что требуется. Кстати, должен работать и в отсутствии интернета, устройствам достаточно быть в локальной сети.


    1. ilyakruchinin
      22.07.2023 20:24

      Полностью поддерживаю.
      Уже давно использую Syncthing для синхронизации между телефонами и компьютером.
      Безопасно, просто, надежно, опенсорсно: https://syncthing.net/