将图库中的多张图片上传到休息方法会导致某些图片未上传

Upload multiple images from gallery to a rest method result in some images not uploaded

编辑

所以我决定进一步研究情况。我试图链接各种函数返回的承诺,但到目前为止我没有运气。

我的应用程序的这一部分设计为不完全异步工作,这就是现在引起巨大头痛的原因。

首先:Imagepicker 本身 returns 一个承诺。在这个承诺中,我拥有用户从图库中选择的所有图片 - 我将限制设置为 5,但这无关紧要。

            this.imagePicker.getPictures(this.options)
            // Prima promise, ottengo le foto scelte dalla gallery
            .then((res) => {
                var count = 1;
                for (var i = 0; i < res.length; i++) {


                    var total = res.length;
                    let path:string = res[i].toString();
                    // Estraggo il nome e il percorso del file
                    var currentName = path.substr(path.lastIndexOf('/') + 1);
                    var correctPath = path.substr(0, path.lastIndexOf('/') + 1);

     })

现在,如果我使用 typescript/javascript 以外的任何其他语言,我会在 for 循环内调用上传方法,每个文件一个,获取该特定文件的结果并调用第二个上传方法(元数据)将完成上传并移动到 for 循环中的下一个文件。

但我不会使用任何其他语言,所以最初我想:"Hey, let's nest the promises!"。哪个有效,几乎完美无缺,直到你开始遇到一些网络问题,然后 bam,你永远不知道哪些文件已上传,哪些文件没有上传,直到最后。

问题是我如何链接来自不同函数的承诺?

我这样试过(上面的代码现在如下图)

            this.imagePicker.getPictures(this.options)
            // Prima promise, ottengo le foto scelte dalla gallery
            .then((res) => {
                var count = 1;
                for (var i = 0; i < res.length; i++) {


                    var total = res.length;
                    let path:string = res[i].toString();
                    // Estraggo il nome e il percorso del file
                    var currentName = path.substr(path.lastIndexOf('/') + 1);
                    var correctPath = path.substr(0, path.lastIndexOf('/') + 1);

                this.file.readAsArrayBuffer(correctPath.toString(), currentName)
                    .then( result => {

                        let blob = new Blob([result], {type: "image/jpeg"});
                        return blob;
                    })
                }

     })

但话又说回来,如果我想使用那个 "blob",我必须在那个 promise 中链接一个 .then,所以无论如何我最终都会嵌套它们。更糟糕的是,我现在知道某些代码将在 promise 取回结果之前执行。而这正是发生的事情。循环继续是因为在调用 this.file.readAsArrayBuffer 之后,代码在等待 promise 实现时跳转并关闭 for 循环迭代,与此同时,循环的第二次迭代触发了一个新的 promise。

上面编辑过的代码片段和下面的原始代码都无法正确显示已显示的 loadingController,但它在下载真正开始之前就消失了,或者,如果我删除持续时间选项并放置上传承诺的 .then 部分中的 this.loadingController.dismiss(),最后一个 loadingController 永远保持打开状态,它永远不会被解雇。

这是原始代码(在找到可行的解决方案之前我仍然使用它)。

要求回顾: - 用户从图库中选择最多 5 张图片 - 5 张照片中的每一张都使用合适的 loadingController 模式一次上传一次,直到上传结束,然后相应的元数据也被上传。 - 下载结束时会显示 Toast,向用户提供反馈 - 从下一个文件开始

在这种特殊情况下,我对用户在上传时可以做其他事情不感兴趣:我希望他们 "stuck" 加载到结束。

原代码如下:

photoPicker()


        photoPicker() {

            if (!this.uploadForm.invalid) {
                this.options = {
                    width: 4000,
                    quality: 100,
                    outputType: 0,
                }

                this.imagePicker.getPictures(this.options)
                .then((res) => {
                    var count = 1;
                    for (var i = 0; i < res.length; i++) {

                        var total = res.length;
                        let path:string = res[i].toString();

                        // Estraggo il nome e il percorso del file
                        var currentName = path.substr(path.lastIndexOf('/') + 1);
                        var correctPath = path.substr(0, path.lastIndexOf('/') + 1);

                        if ( CONFIG.DEV == 1) { 
                            let datetime = new Date();
                            console.log('[objects-docs-multiupload] @ ' + datetime.toISOString() + ' picture path: ');
                            console.log(path);

                        }
                        // Leggo il contenuto in un buffer
                        this.file.readAsArrayBuffer(correctPath.toString(), currentName)
                        .then( result => {


                            if ( CONFIG.DEV == 1) { 
                                let datetime = new Date();
                                console.log('[objects-docs-multiupload] @ ' + datetime.toISOString() + 'data result: ');
                                console.log(result); 
                            }
                            // E uso il contenuto per creare un blob con il binario del file
                            let blob = new Blob([result], {type: "image/jpeg"});
                            if ( CONFIG.DEV == 1) { 
                                let datetime = new Date();
                                console.log('[objects-docs-multiupload] @ ' + datetime.toISOString() + 'data blob: ');
                                console.log(blob);
                            }
                            // invoco il metodo per caricare il file
                            this.uploadFile(blob, currentName, count, total);
                            count = count + 1;
                        });
                    }
                }, (err) => {
                    alert(err);
                });

            } else {

                this.showToastAlert('Compilare i dati del documento', 'error');
                return;
            }

            if (this.uploadForm.controls.recipient.value != '') {

                this.createTask();
            } 
            this.router.navigateByUrl('/objects-dashboard/'+this.obj_id);

        }

上传文件()


        async uploadFile(file, fileName, counter, total) {

            // Chiamo la funzione asincrona per la mascherina di caricamento
            // in modo da dare visibilità al fatto che la app è ferma per fare un upload
            this.presentUpLoading(counter, total);

            // Contatto il metodo uploadFile del rest
            this.restProvider.uploadFile(file, fileName)
            .then(data => {
                if ( CONFIG.DEV == 1) { 
                    let datetime = new Date();
                    console.log('[objects-dashboard] @ ' + datetime.toISOString() + 'response from uploadFile: ');
                    console.log(data);
                }
                // Qui devo fare un piccolo trucco
                // devo ritrasformare in json e quindi rifare il parse
                // per otternere un oggetto (recryptData) da usare per assemblare
                // l'url cui fare il redirect
                let decryptedData = JSON.stringify(data);
                let recryptData = JSON.parse(decryptedData);
                this.loadDoc(recryptData.uuid, recryptData.uploadName, counter, total);

            }); 
        }

loadDoc()


        async loadDoc(uuid, uploadName, counter, total) {

            let upload = {
                name: this.uploadForm.controls.name.value+'_'+counter,
                categoryId: this.uploadForm.controls.cat_id.value.substring(3),
                description: this.uploadForm.controls.description.value+'_'+counter,
                filename: uploadName,
                uuid: uuid,
                objectId: this.obj_id,
            };
            if(CONFIG.DEV ==1) { console.log(upload); };
            this.restProvider.uploadDoc(upload)
            .then( data => {

                if (data['Result'] == 'Success') {

                    // OK: Richiamo la funzione showToastAlert
                    // per mostrare l'avviso Toast
                    this.showToastAlert('Documento Caricato '+counter+' di '+total, 'success');
                    // Rimando alla pagina messages-dashboard
                    //this.router.navigateByUrl('/objects-dashboard/'+this.obj_id);             

                } else {

                    // KO: Richiamo la funzione showToastAlert
                    // per mostrare l'avviso Toast
                    this.showToastAlert('Documento NON caricato '+counter+' di '+total, 'error');               
                }

            });
        }

我终于设法让整个事情正常工作。 事实证明我以错误的方式调用了函数。我以正确的方式使用 await/async 解决了问题。

for..loop 内部:

            this.imagePicker.getPictures(this.options)
            // Prima promise, ottengo le foto scelte dalla gallery
            .then(async (res) => {
                var count = 1;
                for (var i = 0; i < res.length; i++) {

                    var total = res.length;

                    let path:string = res[i].toString();
                    // Estraggo il nome e il percorso del file
                    var currentName = path.substr(path.lastIndexOf('/') + 1);
                    var correctPath = path.substr(0, path.lastIndexOf('/') + 1);

                    // Leggo il contenuto in un buffer
                    await this.file.readAsArrayBuffer(correctPath.toString(), currentName)
                    .then( (result) => {
                        // E uso il contenuto per creare un blob con il binario del file
                        this.blob = new Blob([result], {type: "image/jpeg"});
                        return this.blob;
                    })

                    // Visualizzo il loading controller che indica il caricamento in corso
                    if ( CONFIG.DEV == 1 ) console.log("Presenting loading controller for counter "+count+" of "+total);
                    this.presentUpLoading(count, total);
                    // Inizio l'upload del file col metodo rest e aspetto l'esito
                    if ( CONFIG.DEV == 1 ) console.log("Starting upload of "+currentName);
                    await this.uploadFile(this.blob, currentName,count, total);
                    if ( CONFIG.DEV == 1 ) console.log("RecryptData: ");
                    if ( CONFIG.DEV == 1 ) console.log(this.recryptData);
                    if ( CONFIG.DEV == 1 ) console.log("Loading Doc Metadata");
                    // Carico i metadati dei files e aspetto l'esito
                    await this.loadDoc(this.recryptData.uuid, this.recryptData.uploadName, count, total);
                    if ( CONFIG.DEV == 1 ) console.log("Dismissing loading controller");
                    // Elimino il loading controller e passo al successivo upload
                    this.dismissUpLoading();
                    if ( CONFIG.DEV == 1 ) console.log("incrementing counter");
                    count++;
                }
            })

那个技巧是让 getPictures 的承诺结果成为一个异步函数,这样我就可以在循环中必须 运行 的所有其他承诺中使用 await 。 这样,在下一次执行循环之前,所有的承诺都得到了履行,而且我还能够修复 loadingController,它现在在整个上传时间内保持打开状态。

为了让实际的上传函数与 await 一起工作,我必须将该函数的结果转换为 Promise 本身:

    uploadFile(file, fileName, counter, total) {

        // Chiamo la funzione asincrona per la mascherina di caricamento
        // in modo da dare visibilità al fatto che la app è ferma per fare un upload
        return new Promise((resolve) => {
            // Contatto il metodo uploadFile del rest
                this.restProvider.uploadFile(file, fileName)
                .then((data) => {
                    // Qui devo fare un piccolo trucco
                    // devo ritrasformare in json e quindi rifare il parse
                    // per otternere un oggetto (recryptData) da usare per assemblare
                    // l'url cui fare il redirect
                    let decryptedData = JSON.stringify(data);
                    this.recryptData = JSON.parse(decryptedData);
                    //this.loadDoc(this.recryptData.uuid, this.recryptData.uploadName, counter, total);
                    resolve(this.recryptData);
                }); 
        });
    }

因此,数组 "recryptData" 可用于下一个函数 (loadDoc),我能够避免 promise 嵌套。