Оборачиваем обещания JS в функцию

У меня есть веб-приложение, которое собирает различные файлы с URL-адресов и объединяет их в zip-архив.

Я использую JSZip для работы с zip-файлами. Вот пример кода, который включает содержимое другого zip-архива, расположенного на том же сервере:

<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>   
   <script type="text/javascript" src="./js/jszip.js"></script>
    <script type="text/javascript" src="./js/jszip-utils.js"></script>
    <script type="text/javascript" src="./js/FileSaver.js"></script>

Downloading archive with contents from another archive



<script>

jQuery(function($) {

            var zip = new JSZip();
            zip.file('see.txt','Regular files are being included with a single function call');



            function flash(){
            JSZipUtils.getBinaryContent('/version.zip', function(err, data) {
                try {
                  zip.loadAsync(data)
                  .then(function(zip) {
                        zip.generateAsync({type: 'blob'},
                        function(metadata) {
                        })
                        .then(function(blob) {
                            saveAs(blob, 'result.zip');

                        }, function(e) {
                            showError(e);
                        });
                  })
                  .then(function success() {

                  });
                } catch(e) {console.log(e)}
              });
            };


        flash();



        return false;

});

</script>

Работа с JSFiddle с включенными библиотеками и примерами

Это работает просто отлично. JSZipUtils.getBinaryContent возвращает обещание с двоичным содержимым zip-файла из URL-адреса. zip.loadAsync() читает бинарное содержимое и включает его в архив. zip.generateAsync создает представление zip-архива в оперативной памяти, чтобы мы могли загрузить его позже.

Однако мне нужно обернуть zip.loadAsync() в функцию, которую я могу вызывать несколько раз. Я пробовал что-то вроде этого:

function zipmerge(source){
JSZipUtils.getBinaryContent(source, function (err, data) {
   if(err) {
      throw err; // or handle the error
   }
   var zip = new JSZip();
   zip.loadAsync(data);
});
};

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

Я не очень хорошо разбираюсь в том, как работают обещания, поэтому мне нужна помощь с функцией, которая будет получать URL-адрес и ждать разрешения обещания, чтобы я мог вызывать его несколько раз вместе с обычным zip.file, а затем запускать генерацию zip. Спасибо.


person mkrl    schedule 01.05.2017    source источник
comment
Можете ли вы уточнить, какова ваша цель? Вы пытаетесь прочитать несколько zip-файлов с помощью loadAsync и объединить их в один zip?   -  person nderscore    schedule 01.05.2017
comment
Похоже, вы пытаетесь заставить функцию ждать, пока обещание не будет разрешено, прежде чем оно вернется. Вы не можете сделать это в Javascript. Невозможно взять асинхронную операцию и каким-то образом обернуть ее синхронной функцией, которая возвращает асинхронный результат. Этого нельзя сделать. Его часто просят, и это невозможно сделать. Вместо этого вам нужно научиться писать правильный асинхронный код, в котором вы возвращаете обещание из своей функции и используете .then() в этом обещании, чтобы узнать, когда асинхронная операция выполнена, а затем и только тогда внутри этого обработчика .then() вы инициируете следующий шаг в ваша последовательность.   -  person jfriend00    schedule 01.05.2017
comment
@nderscore да, это то, что я пытаюсь сделать. Можно предположить, что мы не знаем, сколько архивов нужно объединить / пользователь может добавить дополнительные архивы.   -  person mkrl    schedule 01.05.2017
comment
Кроме того, throw err из асинхронного обратного вызова не сделает ничего полезного. Ошибка, скорее всего, будет просто молча съедена, и нет возможности catch этого исключения на более высоком уровне, потому что оно вызвано асинхронным обратным вызовом. Так что, по сути, никогда не делайте throw err из асинхронного обратного вызова.   -  person jfriend00    schedule 01.05.2017
comment
Убедитесь, что все ваши асинхронные функции (включая then обратные вызовы!) return обещания   -  person Bergi    schedule 01.05.2017


Ответы (1)


Вы можете обернуть метод JSZipUtils.getBinaryContent в Promise и сгенерировать их массив. Затем вы можете использовать Promise.all() для создания нового обещания, которое разрешается, когда разрешаются все обещания в массиве.

Вот пример того, как я бы это сделал:

// function to read in a list of source zip files and return a merged archive
function mergeZips(sources) {
    var zip = new JSZip();

    return readSources(sources, zip)
        .then(function() {
            return zip;
        });
}

// generate an array of promises for each zip we're reading in and combine them
// into a single promise with Promise.all()
function readSources(files, zip) {
    return Promise.all(
        files.map(function(file){
            return readSource(file, zip);
        })
    );
}

// promise-ified wrapper function to read & load a zip
function readSource(file, zip) {
    return new Promise(function(resolve, reject) {
        JSZipUtils.getBinaryContent(file, function (err, data) {
            if (err) {
                reject(err);
            }
            // resolving the promise with another promise will pass the promise
            // down the chain:
            resolve(zip.loadAsync(data)); 
        });
    });
}

// example usage:
mergeZips([
    'file1.zip',
    'file2.zip',
    'file3.zip'
]).then(function(zip) {
    zip.generateAsync({type: 'blob'})
        .then(function(blob){
            saveAs(blob, 'result.zip')
        })
});
person nderscore    schedule 01.05.2017
comment
Спасибо, я понял идею. Но код, который вы предоставили, не работает, я попытаюсь понять, что здесь не так. - person mkrl; 01.05.2017
comment
@mkrl Я смог исправить код, и он заработал. Я обновил свой ответ, и вот демонстрация jsfiddle: jsfiddle.net/ghvw7brt - person nderscore; 01.05.2017