将图库中的多张图片上传到休息方法会导致某些图片未上传
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 嵌套。
编辑
所以我决定进一步研究情况。我试图链接各种函数返回的承诺,但到目前为止我没有运气。
我的应用程序的这一部分设计为不完全异步工作,这就是现在引起巨大头痛的原因。
首先: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 嵌套。