【问题标题】:Async with recursive method与递归方法异步
【发布时间】:2022-01-06 05:15:53
【问题描述】:

我正在尝试制作一种使用驱动器 API 在谷歌驱动器中复制文件夹树的方法。我遇到了一些异步方法不起作用的问题,如果有人能发现代码中的缺陷,或者向我指出一些有用的文章,那将非常有帮助。

我需要等待导致错误的行,因为下一次递归调用需要使用“复制”功能所需的文件夹的 ID。它返回一个承诺,所以它看起来应该可以工作。

一般来说,我对异步编程不是很有经验,所以我可能会忽略一些简单的事情,但我已经坚持了一段时间了。谢谢!!

代码:

function authorize() {
    const auth = new google.auth.GoogleAuth({
        keyFile: 'creds.json',
        scopes: SCOPES
    })
    return google.drive({version: 'v3', auth})
}

function search(driveService, parentFolder) {
    return new Promise((resolve, reject) => {
        driveService.files.list({
            pageSize: 20,
            q: `'${parentFolder}' in parents and trashed = false`, 
            fields: 'files(id, name, mimeType)'
        }).then(({data}) => resolve(data))
        .catch(err => reject(err))
    })
}

function createRoot(driveService, CLIENT_NAME, NEW_FOLDER_LOCATION) {
    let fileMetadata = {
        'name': CLIENT_NAME,
        'mimeType': 'application/vnd.google-apps.folder',
        'parents': [NEW_FOLDER_LOCATION]
    }
    return new Promise((resolve, reject) => {
        const file = driveService.files.create({
            resource: fileMetadata,
            fields: 'id'
        }, function(err, file) {
            if(err) {
                reject(err);
            } else {
                resolve(file.data.id);
            }
        })
    })
}

function copy(driveService, copyContentId, contentNewName, root){
    
    let fileMetadata = {
        'name': contentNewName,
        'parents': [root]
    };
    return new Promise((resolve, reject) => {
        const file = driveService.files.copy({
            'fileId': copyContentId,
            'resource': fileMetadata
        }, function(err, file) {
            if(err) {
                reject(err);
            } else {
                resolve(file.data.id);
            }
        })
    })
}

async function recursive(driveService, startSearch, rootFolder) {
    let children = await search(driveService, startSearch);
    if(children.files.length > 0) {
        children.files.forEach((element) => {
            if(element.mimeType === 'application/vnd.google-apps.folder') {
                let name = element.name.replace('Last, First', CLIENT_NAME);
                let folderID = await copy(driveService, element.id, name, rootFolder);
                recursive(driveService, element.id, folderID);
            } else {
                let name = element.name.replace('Last, First', CLIENT_NAME);
                let fileID = await copy(driveService, element.id, name, rootFolder);
            }
        })
    } else {
        console.log('end of branch');
    }
}

async function run() {
    try{

        const google = await authorize();
        const root = await createRoot(google, CLIENT_NAME, NEW_FOLDER_LOCATION);
        await recursive(google, START_SEARCH, root);

    } catch(err) {
        console.log('error', err);
    }
}

run();

输出:

app.js:75
                let folderID = await copy(driveService, element.id, name, rootFolder);
                               ^^^^^

SyntaxError: await is only valid in async functions and the top level bodies of modules
    at Object.compileFunction (node:vm:352:18)
    at wrapSafe (node:internal/modules/cjs/loader:1031:15)
    at Module._compile (node:internal/modules/cjs/loader:1065:27)
    at Object.Module._extensions..js (node:internal/modules/cjs/loader:1153:10)
    at Module.load (node:internal/modules/cjs/loader:981:32)
    at Function.Module._load (node:internal/modules/cjs/loader:822:12)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:81:12)
    at node:internal/main/run_main_module:17:47

【问题讨论】:

  • 我想知道你为什么要为递归而烦恼。做一个 file.list 并使用 nextpage 令牌下载所有内容。然后在本地进行排序。如果你要递归遍历文件夹,你会很快吃掉你的配额。
  • @DaImTo 很好,但我不确定如何实现相同的结构,我要复制的文件夹可能会有多个级别的子文件夹。

标签: node.js google-drive-api asynchronous-javascript


【解决方案1】:

切换

children.files.forEach(element => { ... })

到:

for (let element of children.files) { ...}

然后,改变这个:

recursive(driveService, element.id, folderID);

到这里:

await recursive(driveService, element.id, folderID);

您想切换.forEach(),这样您就不会创建新的函数范围,因此您可以使用await,而await 将暂停父函数。对.forEach() 的回调中的新函数未声明aysnc,这就是您收到错误的原因(您不能在该回调函数中使用await)。而且,将该回调声明为async 将使错误消失,但不会执行您想要的操作,因为它不会暂停父函数。

.forEach() 不支持异步/等待。不要在这种情况下使用它。

我什至会说.forEach() 现在已经过时了。由于for/oflet/const 是块作用域,而.forEach() 不支持async/await,因此有很多理由避免使用.forEach(),也没有理由使用它。


您需要await recursive(...),以便将新的承诺链绑定到当前的承诺链,而不是创建一个全新的独立承诺链,调用者将不知道何时完成。

【讨论】:

  • 这行得通!感谢@jfriend00的帮助