如何创建异步代码?

How do I create Async code?

嘿,所以我正在使用 discord.js 为 discord 机器人制作排行榜 我想按用户名而不是 ID 显示用户,所以使用 discord.js 我使用函数 .fetchUser(ID)

.fetchUser(ID) 是一个承诺,可能需要一些时间,具体取决于带宽。

所以因为 discord.js 使用 promise 我的代码不再是异步的,我 认为 通过将代码放在 promise 中它会 运行 异步.

我错了。

我的代码:

//This is ran inside a .prototype function so (this) is defined
return new Promise((resolve, reject) => {
    this.list = [];
    //users is an object with user's IDs as the key
    //Currently it only has one key in it (mine)
    for (let i in users) {
            let pos = 0;
            let score = this.getScore(users[i]);
            if (score === 0) {
                client.fetchUser(i).then((user)=> {
                    console.log(`pushed`);//logs way after the "finish" is logged 
                    this.list.push([user.username.substring(0,13), score])
                });
                continue;
            }
            for (let h = 0; h < this.list.length; h++) {
                if (score >= this.list[h][1]) {
                    pos = h;
                    break;
                }
            }
            client.fetchUser(users[i].id).then((user) => {
                this.list.splice(pos, 0, [user.username.substring(0,13), score])
            })
        }
        console.log(`Finished: `+this.list.length);
        resolve(this.list);
})

您必须将收到的 Promise 链接起来。 Client#fetchUser() returns 一个 Promise 正在 等待,但还不够。您必须向上传播 Promises。如果你的函数调用链中的某些东西是异步的,你应该考虑整个链异步。

您从 fetchUser(...).then(...) 中填充 this.list,这不一定是坏事,只要您在 fetchUser 之后才尝试使用 list的解析链已经完成。你没有那样做;你马上resolve(this.list)

考虑原始函数的这种缩写形式:

return new Promise((resolve, reject) => {
    this.list = [];
    for (let i in users) {
        // A promise is created right here
        client.fetchUser(i).then((user) => {
            // This will populate list AFTER the then callback
            this.list.push([user.username.substring(0, 13), score])
        });
    }
    // You aren't waiting until the promise created by fetchUser completes
    resolve(this.list);
})

this.list 不能被视为 "complete",直到所有相关用户都加载了他们的个人资料并检索了他们的分数。考虑到这一点,我们可以使用 Promise.all() 它接受一个 Promise 的数组,然后在所有提供的承诺都已解决后解决。所以要那样等待,我们会做这样的事情,这仍然不理想,但正确等待:

return new Promise((resolve, reject) => {
    this.list = [];

    // This is an array of Promises
    const discordUsersPromise = users.map(user => client.fetchUser(user));

    // Wait till all the fetchUser calls are done
    const listIsPopulatedPromise = Promise.all(discordUsersPromise).then(dUsers => {
        // This replaces your for (let i in users) {}
        Object.entries(users).forEach((user, idx) => {
            const score = this.getScore(user);
            const discordUser = dUsers[idx];
            this.list.push([discordUser.username.substring(0, 13), score])
        });
    });

    // We still have to wait for the list to be completely populated
    return listIsPopulatedPromise.then(() => this.list);
})

考虑这个实现。我对你的代码做了一些假设,因为你使用 this.list 但不包括 this 的实例,但大部分应该是相同的:

/**
 * Object to composite certain user properties
 * @typedef {RealUser}
 * @property {String} user The local string for the user
 * @property {User} realUser The user that Discord gives us
 * @property {Number} score The score this user has
 */

/**
 * Class to encapsulate user and score and data
 */
class Game {
    /**
     * Constructs a game
     */
    constructor() {
        /**
         * The users we are keeping score of
         * @type {Object}
         */
        this.users = {};
    }
    /**
     * Get the score of a particular user
     * @param {String} user User to get score of
     * @returns {Number} User's score
     */
    getScore(user) {
        return this.users[user] || 0;
    }
    /**
     * Get a composite of users and their status
     * @param {String[]} users The users to put on our leaderboard
     * @returns {Promise<RealUser[]>} Sorted list of users that we included in our leaderboard
     */
    getLeaderBoard(users) {
        // Map all the users that we are given to Promises returned bye fetchUser()
        const allRealUsersPromise = Promise.all(users.map(user => client.fetchUser(user)
            /*
             * Create an object that will composite the string that we use
             * to note the user locally, the Discord User Object, and the
             * current score of the user that we are tracking locally
             */
            .then(realUser => ({
                user,
                realUser,
                score: this.getScore(user)
            }))));

        /*
         * Once we have all the data we need to construct a leaderboard,
         * we should sort the users by score, and hand back an array
         * of RealUsers which should contain all the data we want to
         * print a leaderboard
         */
        return allRealUsersPromise
            .then(scoredUsers => scoredUsers.sort((a, b) => a.score - b.score));
    }
    /**
     * Prints out a leaderboard
     * @param {String[]} users The users to include on our leaderboard
     */
    printLeaderBoard(users) {
        // Go get a leaderboard to print
        this.getLeaderBoard(users).then(sortedScoredUsers => {
            // Iterate our RealUsers
            sortedScoredUsers.forEach((sortedScoredUser, idx) => {
                const username = sortedScoredUser.realUser.username;
                const score = sortedScoredUser.score;
                // Print out their status
                console.log(`${username.substring(0, 13)} is in position ${idx + 1} with ${score} points`);
            });
        });
    }
}

const game = new Game();
game.users["bob"] = 5;
game.users["sue"] = 7;
game.users["tim"] = 3;

game.printLeaderBoard(Object.keys(game.users));