Ionic 3:通过Promise处理权限弹窗,等待用户授权后执行下载
我正在开发一款Ionic 3应用,核心功能是下载图片并保存到指定目录,这部分功能本身已经实现了。但遇到一个问题:卸载重装应用后,当尝试使用之前创建过的同一个文件夹时,首次下载图片会触发Android的存储权限弹窗,但在用户点击“允许”之前,downloadImage函数的错误分支就已经执行了,直接弹出“Download Failed!”的提示。
以下是我当前使用的代码:
downloadImage(imageURL) { var folderName = 'FolderName'; this.createDirectory(folderName).then(() => { const fileTransfer: FileTransferObject = this.transfer.create(); const imageName = imageURL.split('/').pop(); fileTransfer.download(imageURL, this.storageDirectory + 'FolderName/' + imageName).then((entry) => { const alertSuccess = this.alertCtrl.create({ title: `Download Succeeded!`, subTitle: `${imageURL} was successfully downloaded to: ${entry.toURL()}`, buttons: ['Ok'] }); alertSuccess.present(); }, (error) => { const alertFailure = this.alertCtrl.create({ title: `Download Failed!`, subTitle: `${imageURL} was not successfully downloaded. Error code: ${error.code}`, buttons: ['Ok'] }); alertFailure.present(); }); }); } createDirectory(folderName): Promise<Boolean> { return new Promise((resolve, reject) => { if(!this.platform.is('cordova')) { return false; } if (this.platform.is('ios')) { this.storageDirectory = this.file.externalRootDirectory; } else if(this.platform.is('android')) { this.storageDirectory = this.file.externalRootDirectory; } else { return false; } this.file.checkDir(this.storageDirectory, folderName).then((success) => { resolve(true); }) this.file.createDir(this.storageDirectory, folderName, false) .then((success) => { resolve(true); }) .catch((error) => { reject(false); }); }); }
请问是否可以通过Promise或者其他方式延迟后续的下载执行,等用户点击“允许”权限后再尝试下载操作?
这个问题我之前帮朋友排查过,核心就是你在没确认权限是否授予的情况下,就直接跑了文件操作代码——卸载重装后应用权限会重置,首次访问存储时系统弹权限框,但这时候你的createDirectory或者下载请求已经发出去了,因为权限没给直接报错,根本没等用户点“允许”。
给你一套完整的解决思路:
1. 添加权限检查与请求工具函数
首先安装cordova-plugin-android-permissions插件,用来处理Android的存储权限请求:
cordova plugin add cordova-plugin-android-permissions
然后在你的组件中声明插件,并编写权限处理函数:
declare var cordova: any; // ... 其他组件代码 async requestStoragePermission(): Promise<boolean> { if (!this.platform.is('android')) { return true; // iOS或其他平台无需额外处理 } const permissions = cordova.plugins.permissions; const requiredPermissions = permissions.READ_EXTERNAL_STORAGE | permissions.WRITE_EXTERNAL_STORAGE; return new Promise((resolve) => { // 先检查是否已有权限 permissions.checkPermission(requiredPermissions, (status) => { if (status.hasPermission) { resolve(true); } else { // 无权限则弹出系统权限请求框 permissions.requestPermissions(requiredPermissions, (status) => { resolve(status.hasPermission); }, () => { resolve(false); }); } }, () => { resolve(false); }); }); }
2. 改造下载函数,等待权限确认后执行
把downloadImage改成async函数,先调用权限请求函数,等用户处理完权限弹窗后再继续执行下载逻辑:
async downloadImage(imageURL) { // 先获取存储权限,无权限则直接提示用户 const hasPermission = await this.requestStoragePermission(); if (!hasPermission) { const alertPermission = this.alertCtrl.create({ title: `Permission Required`, subTitle: `Storage permission is needed to download images. Please enable it in settings and try again.`, buttons: ['Ok'] }); alertPermission.present(); return; } var folderName = 'FolderName'; try { await this.createDirectory(folderName); const fileTransfer: FileTransferObject = this.transfer.create(); const imageName = imageURL.split('/').pop(); const entry = await fileTransfer.download(imageURL, this.storageDirectory + 'FolderName/' + imageName); const alertSuccess = this.alertCtrl.create({ title: `Download Succeeded!`, subTitle: `${imageURL} was successfully downloaded to: ${entry.toURL()}`, buttons: ['Ok'] }); alertSuccess.present(); } catch (error) { const alertFailure = this.alertCtrl.create({ title: `Download Failed!`, subTitle: `${imageURL} was not successfully downloaded. Error code: ${error.code}`, buttons: ['Ok'] }); alertFailure.present(); } }
3. 修复目录创建函数的Promise逻辑
你的createDirectory函数存在逻辑问题:同时调用checkDir和createDir会导致Promise被多次resolve,应该先检查目录是否存在,不存在再创建:
createDirectory(folderName): Promise<boolean> { return new Promise((resolve, reject) => { if (!this.platform.is('cordova')) { reject(false); return; } if (this.platform.is('ios') || this.platform.is('android')) { this.storageDirectory = this.file.externalRootDirectory; } else { reject(false); return; } this.file.checkDir(this.storageDirectory, folderName) .then(() => { resolve(true); // 目录已存在,直接返回成功 }) .catch(() => { // 目录不存在,执行创建操作 this.file.createDir(this.storageDirectory, folderName, false) .then(() => resolve(true)) .catch(() => reject(false)); }); }); }
原理说明
通过async/await(本质是Promise的语法糖),我们可以让代码暂停在权限请求步骤,直到用户处理完系统权限弹窗——不管是点击“允许”还是“拒绝”,拿到明确结果后才继续执行后续的目录检查和下载操作,这样就不会出现权限弹窗还未处理,下载逻辑就提前报错的情况了。
另外,修复目录创建函数的逻辑,能避免潜在的Promise重复触发问题,让整个流程更稳定。
内容的提问来源于stack exchange,提问作者Stranger

