【问题标题】:Node script does not exit after all setTimeout finished所有 setTimeout 完成后节点脚本不退出
【发布时间】:2020-03-09 15:18:35
【问题描述】:

我有这个脚本可以更改我们用于测试目的的现有 FireBase 身份验证用户的所有密码,以便将用户重置为“干净”状态。

var admin = require('firebase-admin');

// Input args
var jsonKey = process.argv[2];
var projectId = process.argv[3];
var newPassword = process.argv[4];

var serviceAccount = require(jsonKey);

admin.initializeApp({
    credential: admin.credential.cert(serviceAccount),
    databaseURL: "https://" + projectId + ".firebaseio.com"
});

// FIXME Only for debug purposes
var index = 1;

/**
* https://firebase.google.com/docs/auth/admin/manage-users#update_a_user
*/
function nextUser(uid, timeout) { 
    setTimeout(() => {
        admin.auth().updateUser(uid, { 
            password : newPassword
        })
        .then(function() {
            console.log(index++ + ') Successfully updated user', uid);
        })
        .catch(function(error) {
            console.log(index++ + ') Error updating user:', error);
        });
    }, timeout);
}

/**
* https://firebase.google.com/docs/auth/admin/manage-users#list_all_users
* https://stackoverflow.com/a/58028558
*/
function listAllUsers(nextPageToken) {
    let timeout = 0;
    admin.auth().listUsers(1000, nextPageToken)
        .then(function (listUsersResult) {
            console.log("Retrieved " + listUsersResult.users.length + " users");

            listUsersResult.users.forEach(function (userRecord) {
                timeout = timeout + 100
                nextUser(userRecord.uid, timeout)
            });

            if (listUsersResult.pageToken) {
                listAllUsers(listUsersResult.pageToken);
            }
        })
        .catch(function (error) {
            console.log('Error listing users:', error);
        });
}

listAllUsers();

脚本从本地机器启动

node <filename.js> <path of jsonkey> <projectid> <new password>

正如您所见,迭代器运行时超时,这是在我遇到“更新帐户信息的超出配额”问题后需要的,我尝试使用this answer 解决该问题

脚本运行良好,所有用户都被正确处理(我为这种调试添加了一个顺序计数器)并且没有抛出“配额”错误。问题是在脚本结束时,shell 提示符没有退出,进程仍然挂起,我需要一个CTRL+C 来杀死它。除了这种行为令人讨厌的部分之外,这还阻止了一个更广泛的脚本,该脚本调用这个脚本并且不会继续执行下一行命令。

看来setTimeout函数全部执行完毕后,主进程并没有自动关闭。

我该如何解决这个问题?


编辑

我尝试了不同的方法:

第二个版本有效,但速度很慢。所以我从初始脚本开始,我添加了一个“监视器”,等待所有用户完成(使用非常脏的计数器)并在最后关闭脚本

var admin = require('firebase-admin');

// Input args
var jsonKey = process.argv[2];
var projectId = process.argv[3];
var newPassword = process.argv[4];

var serviceAccount = require(jsonKey);

admin.initializeApp({
    credential: admin.credential.cert(serviceAccount),
    databaseURL: "https://" + projectId + ".firebaseio.com"
});

// Index to count all users that have been queued for processing
var queued = 0;

// Index to count all user that have completed the task
var completed = 0;

/**
* https://firebase.google.com/docs/auth/admin/manage-users#update_a_user
*/
function nextUser(uid, timeout) { 
    setTimeout(() => {

        /* ------ Process User Implementation ------ */
        admin.auth().updateUser(uid, { 
            password : newPassword
        })
        .then(function() {
            console.log(++completed + ') Successfully updated user ', uid);
        })
        .catch(function(error) {
            console.log(++completed + ') Error updating user:', error);
        });
        /* ------ Process User Implementation ------ */

    }, timeout);

    queued++;
}

/**
* https://firebase.google.com/docs/auth/admin/manage-users#list_all_users
* https://stackoverflow.com/a/58028558
*/
function listAllUsers(nextPageToken) {
    let timeout = 0;
    admin.auth().listUsers(1000, nextPageToken)
        .then(function (listUsersResult) {
            console.log("Retrieved " + listUsersResult.users.length + " users");

            listUsersResult.users.forEach(function (userRecord) {
                timeout = timeout + 100
                nextUser(userRecord.uid, timeout)
            });

            if (listUsersResult.pageToken) {
                listAllUsers(listUsersResult.pageToken);
            } else {
                waitForEnd();
            }
        })
        .catch(function (error) {
            console.log('Error listing users:', error);
        });
}

function waitForEnd() {
    if(completed < queued) {
        console.log("> " + completed + "/" + queued + " completed, waiting...");
        setTimeout(waitForEnd, 5000);
    } else {
        console.log("> " + completed + "/" + queued + " completed, exiting...");
        admin.app().delete();
    }
}

listAllUsers();

有效,速度很快。对我来说足够了,因为它是一个测试脚本????

【问题讨论】:

  • 我不知道 Firebase,但有什么方法需要关闭数据库连接吗?当您认为代码完成时,node.js 不会自动退出这一事实意味着某些网络连接、计时器或其他异步操作仍处于打开状态或待处理状态。我所看到的可能是您的应用程序中可能是一个开放的数据库连接。
  • 我认为如果你使用 async/await 语法和标准的“sleep”机制来分隔调用,这对你自己来说会更容易。当您的循环完成后,您可以在最后一次更新完成后调用 admin.app().delete() 来关闭 admin SDK。标准循环还可以防止可能的内存不足情况同时启动过多的这些延迟调用。
  • @DougStevenson 我按照你的建议尝试了异步/等待,这是当前代码:repl.it/repls/RoundedAnchoredDos 我尝试了很多次,但我无法找到一个完全有效的解决方案。首先,我必须删除所有内部方法以尊重睡眠(现在所有代码都在 main 方法中)。现在的问题是在处理所有用户之前调用admin.app().delete();
  • @DougStevenson 这是一个更“同步”的版本,它不需要睡眠,因为所有调用都是链接的:repl.it/repls/DelightfulUnfoldedHexagon 当然现在非常慢,因为每次只进行 1 个调用。但至少它有效,当用户完成时,该过程正确结束。

标签: javascript node.js firebase firebase-authentication firebase-admin


【解决方案1】:

尝试在 setTimeout 函数之后加入以下退出代码。

process.exit() // exit

我更新了代码。

function listAllUsers(nextPageToken) {
    let timeout = 0;
    admin.auth().listUsers(1000, nextPageToken)
        .then(function (listUsersResult) {
            console.log("Retrieved " + listUsersResult.users.length + " users");

            listUsersResult.users.forEach(function (userRecord) {
                timeout = timeout + 100
                nextUser(userRecord.uid, timeout)
            });

            if (listUsersResult.pageToken) {
                listAllUsers(listUsersResult.pageToken);
            }
            process.exit(0); // exit script with success
        })
        .catch(function (error) {
            console.log('Error listing users:', error);
            process.exit(1); // exit script with failure
        });
}

此函数将退出 Node.js 脚本。

process.exit([代码]) 使用指定的代码结束进程。如果省略,则退出使用“成功”代码 0。

使用“失败”代码退出:

process.exit(1);

执行节点的 shell 应该看到退出代码为 1。

官方文档:https://nodejs.org/api/process.html#process_exit_codes

【讨论】:

  • 这是行不通的,因为代码会在交错延迟时启动大量对 setTimeout 的调用。只有在最后一个完成后,脚本才会终止。
  • 是的.. 但这是一种以编程方式退出脚本的方法。如果有任何东西阻止了 process.exit(),那么我认为它可能还有另一个问题。
  • 我刚刚更新了 listAllUsers() 函数。它不是 nextUser 功能。希望对你有用)
  • process.exit() 确实会立即终止进程。不幸的是,此代码仍然不起作用,因为它会在任何用户更新之前退出。所有 API 都是异步的,并立即返回一个表示完成的承诺。
猜你喜欢
  • 2021-10-20
  • 1970-01-01
  • 2017-10-07
  • 2011-12-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多