Как мне создать асинхронный код? - PullRequest
0 голосов
/ 06 июня 2018

Привет, я создаю таблицу лидеров для бот-диска, используя discord.js И я хочу отображать пользователей по их именам вместо их идентификаторов, поэтому при использовании discord.js я использую функцию .fetchUser(ID)

.fetchUser(ID) это обещание, которое может занять некоторое время в зависимости от пропускной способности.

Так как discord.js использует обещание, мой код больше не является асинхронным, я подумал , что, поместив кодв обещании он запустит Async.

И я ошибся.

мой код:

//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);
})

1 Ответ

0 голосов
/ 06 июня 2018

Вы должны отцепить Promise с, которые вы получаете.Client#fetchUser() возвращает Promise, который вы ожидаете , но недостаточно.Вы должны размножаться до Promise с.Если что-то в вашей цепочке вызовов функций асинхронно, вы должны рассматривать всю цепочку как асинхронную.

Вы заполняете this.list из fetchUser(...).then(...), что не обязательно плохо, если вы этого не делаетепытайтесь использовать list до тех пор, пока не будет завершена цепочка разрешений fetchUser.Вы этого не делаете;ты сразу 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 нельзя считать завершенной, пока все участвующие пользователи не загрузят свои профили и не получат свои оценки.Учитывая это, мы можем использовать Promise.all(), который принимает массив Promise s, а затем разрешает, как только все предоставленные обещания разрешены.Поэтому, чтобы подождать таким образом, мы бы сделали что-то подобное, что все еще не идеально, но ждет правильно:

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));
...