Promise 总是解析为 null,尽管在异步函数中返回了一个值

Promise always resolves to null, despite that a value is returned in async function

我的 async 函数有问题。

在内部 promise then 回调中,值设置正确,但返回此变量后,promise 解析,调用者总是得到一个空值!

注意:这是针对不和谐机器人的:我尝试使用他们的 ID 获取用户的显示名称。

这是 async 函数:

export async function getUserInfo(userNameLooking: string, guild: Guild): Promise<UserSettings | null> {

  console.log("Looking for user", userNameLooking);

  userList.forEach(user => {

    console.log("Analyzing user ID", user);

    let thanos = guild.client.users.fetch(user);
    thanos.then(function (result1) {

      console.log("... This ID user name is", result1.username);

      if (result1.username.toLowerCase() == userNameLooking.toLowerCase()) {
        console.log("... Match !");
        console.log(cacheUser[user] );

        return  cacheUser[user] ;
      }
      else {
        console.log("... Not match ...");
      }
    }, function (){console.log("ERROR : Can't find name of ID", user)});
  })

  return null;
}

调用上述函数的代码:

var user;

getUserInfo(args.userName, message.guild).then(function (result1) {
  console.log("Caller Result :", result1); // <--- always null!
  user = result1;

  if (user == null) {
    return message.channel.send("User is unknown");
  }

  const embed = new MessageEmbed();

  embed.setTitle("NAME: " + user.userId);
});

控制台输出:

Looking for user totolitoto
Analyzing user ID 752614956918112386
... This ID user name is TotoLitoto
... Match !
{
  _id: 60abd6dada6f9ad06fbfb9eb,
  userId: '752614956918112386',
  userName: 'TotoLitoto',
  userLang: 'en'
}
Caller Result : null

有什么问题?

你的函数 getUserInfo 只有一个 return 语句,而且它 return 是 null,所以它不可能 return 其他数据为了解决问题的承诺。

问题是您的数据 return 在 forEach 回调函数中编辑。但是 return 值将被遗忘。在 forEach 回调中返回数据是无用的。它没有任何用途。

你必须 return 所有承诺对象,你应该使用 .map,而不是 .forEach。然后使用 Promise.all 等待所有这些承诺解决。然后使用 .find 在那些不是 undefined 的值中找到第一个解析值。这是您希望 getUserInfo 承诺解决的值:所以 return that.

这是它的工作原理:

export async function getUserInfo(userNameLooking: string, guild: Guild): Promise<UserSettings | null> {
    let results = await Promise.all(userList.map(user => {
//                      ^^^^^^^^^^^          ^^^ 
        let thanos = guild.client.users.fetch(user);
        return thanos.then(function (result1) {
//      ^^^^^^ 
            if (result1.username.toLowerCase() == userNameLooking.toLowerCase()) {
                return  cacheUser[user];
            }
        });
    });
    // Find the first non-undefined result, 
    // ... assuming that `catchUser[user]` is a truthy value.
    return results.find(Boolean);
}

注意:我删除了错误处理,所以只关注问题。

捷径

为了避免在我们感兴趣的承诺已经解决时等待剩余的承诺,您可以使用 Promise.any:

// In this version the function does not really need to be declared `async`:
export function getUserInfo(userNameLooking: string, guild: Guild): Promise<UserSettings | null> {
    return Promise.any(userList.map(user => {
//                 ^^^ 
        let thanos = guild.client.users.fetch(user);
        return thanos.then(function (result1) {
            if (result1.username.toLowerCase() == userNameLooking.toLowerCase()) {
                return  cacheUser[user]; // <-- will resolve the `.any` promise
            }
            // Throwing will avoid resolving the `.any` promise
            //  ... unless this is the last one, and there was no match found
            throw new Error("no match"); 
        });
    }).catch(() => null); // resolve to null when nothing matches
}

Trincot 的回答将 运行 针对所有用户的查询,但即使在找到第一个结果后,它也会等待 所有 查询完成后再解析结果.这就是他在回答中使用 Promise.all 的效果。

我们真正想要的是一个像 Array.prototype.find 一样工作但接受一系列承诺的函数。它应该 运行 每个 test 功能,但 resolve 一旦 第一个 value 通过测试。如果没有承诺值通过测试,则解析undefined -

async function find(promises, test) {
  return new Promise((resolve, reject) => {
    for (const p of promises)
      p.then(value => test(value) && resolve(value), reject)
    Promise.all(promises).then(_ => resolve(undefined))
  })
}

find 编写为通用函数很有用,因为它允许 getUserInfo 仅专注于其特殊关注点。它还使我们能够在程序中需要的其他地方重用 find 而无需编写复杂的代码 -

function getUserInfo(query, guild) {
  return find
    ( userList.map(user => guild.client.users.fetch(user))
    , result =>
        result.username.toLowerCase() == query.toLowerCase()
    )
    .then(user => user && cacheUser[user])
}

让我们使用下面的演示程序看看 find 的实际效果 -

async function find(promises, test) {
  return new Promise((resolve, reject) => {
    for (const p of promises)
      p.then(value => test(value) && resolve(value), reject)
    Promise.all(promises).then(_ => resolve(undefined))
  })
}

const sleep = ms =>
  new Promise(r => setTimeout(r, ms))
  
async function fetch(query) {
  await sleep(Math.random() * 3000)
  return query
}

const queries =
  [ "Alice", "Bob", "Carla", "Dina" ]
  
find(queries.map(fetch), result => result.length > 4)
  .then(console.log, console.error) // "Carla" or "Alice"
  
find(queries.map(fetch), result => result.length > 5)
  .then(console.log, console.error) // undefined