Complementando um pouco a resposta do EstevamOt, já que você tá começando a estudar funções assíncronas.

Quando a gente trabalha com Promise e Callbacks a ideia é que estamos avisando pro "código": olha, isso aqui pode demorar um pouco, então ao invés de bloquear a execução de tudo pode ir fazendo o que precisa e quando eu terminar eu te aviso.

Então pegando como exemplo a função:

/**
 * Imagina que é uma chamada a alguma API
 * 
 * @returns {Promise<number>}
 */
function pegaIdade() {
    return new Promise(function (resolve, reject) {
        const idade = Math.floor(Math.random() * 100);
        
        // Caso deu tudo certo
        if (idade) {
            resolve(idade);
        } else {
            reject("Idade Inválida");
        }
    });
}

A função pegaIdade faria alguma consulta a alguma API e retornaria uma Promise. A Promise aceita 2 funções de callback, uma que é executada quando a operação ocorre com sucesso (resolve) e outra para quando tem erro na operação (reject).

A Promise aceita, por meio de uma interface fluente, indicar o que será executado quando terminar. No caso de sucesso executará o then e no caso de falha pulará direto para o catch.

pegaIdade()
    .then(idade => console.log(idade))
    .catch(error => console.error(error))

O bom de usar Promise é que você pode executar todas de uma única vez e então ter só 1 callback pra executar após todas retornarem algo. Muito útil quando a ordem de execução de uma função não depende da outra.

Promise
    .all([pegaIdade(), pegaIdade(), pegaIdade()])
    .then(([idade1, idade2, idade3]) => {
        console.log("idade1: " + idade1);
        console.log("idade2: " + idade2);
        console.log("idade3: " + idade3);
    })
    .catch(error => console.error(error))

Promise também permite encadeamento do retorno, para quando você precisa tratar o dado antes de ir pra próxima etapa.

pegaIdade()
    .then((idade) => {
        if (idade < 18) {
            throw "Menores não permitidos"
        }

        // Esse return jogará uma nova resposta que será executada pelo próximo then
        return idade % 2 ? false : true;
    })
    .then(ePar => console.log(ePar))
    .catch(error => console.error(error))

Quando fazemos esse encadeamento de then é porque uma resposta depende de alguma chamada assíncrona. Mas ir encadeando um monte de then pode deixar o código meio confuso.

Aí entram o async/await marcando a função.

Dentro de uma função marcada com async podemos indicar que vamos esperar a Promise executar e retornar o seu valor ao invés de aguardar a Promise chamar nossa callback. Isso fazemos com o await. Desta forma quando temos várias chamadas a funções assíncronas e que uma chamada dependa da anterior fazemos um código mais linear ao invés de vários encadeamentos.

Mas quando fazemos desta forma o erro que a Promise dispara vira uma exceção. Por isso precisamos tratar com try...catch.

/**
 * @returns {Promise<boolean>}
 */
async function reserva() {
    try {
        const idade = await pegaIdade();
        
        if (idade < 18) {
            throw "Menores não permitidos";
        }

        // Imagina que aqui retorna um boolean
        const reservaEfetuada = await efetuaReserva();

        return reservaEfetuada
    } catch (error) {
        console.error(error);
        return false;
    }
}

Importante também ressaltar que quando marcamos uma função com async estamos transformando ela numa Promise.

Olá, tudo bem brunogasparetto? Cara, sensacional essa explicação mais detalhada da Promise que você deu, realmente vale a pena ler.