Await возвращает ожидающие обещания в Typescript

Я разработчик .Net, новичок в мире javascript/node, и у меня возникли проблемы с запросом на получение HTML с веб-страницы с использованием запроса-обещания. Я пробовал варианты кода ниже.

import cheerio = require('cheerio');
import request = require('request-promise');

export class Address {
    public html;
    public $: CheerioStatic;

    constructor() {
         this.html = this.makeRequest();
    }

    private async makeRequest() {
        const options = {
            uri: 'https://www.google.com/',
            transform(body) {
                return cheerio.load(body);
            }
        };
        return await request(options);
    }
}

Проблема в том, что когда я устанавливаю переменную this.html, вызывая метод this.makeRequest(), я возвращаю Promise { pending }. Я не уверен, почему это так. Из того, что я исследовал, не ждет ли, ждет ли обещание быть решенным? Я пробовал и с синтаксисом .then, но получил еще более странный результат: Promise {_bitField: 0, _fulfillmentHandler0: undefined, _rejectionHandler0: undefined, _promise0: undefined, _receiver0: undefined, …}. Версия makeRequest(), которая возвращает это странное обещание, приведена ниже.

private makeRequest() {
    const options = {
        uri: 'https://www.google.com/',
        transform(body) {
            return cheerio.load(body);
        }
    };
    return request(options)
        .then(function($: CheerioStatic) {
            return $;
        })
        .catch(function(err) {
            //
        });
}

Я хотел бы придерживаться синтаксиса async/await, но буду признателен за любую помощь в объяснении, почему мой код не работает и что я могу сделать, чтобы исправить это!

Обновлять

По предложению @basarat я сделал еще один асинхронный метод, используя ключевое слово await для установки свойства. Мой код ниже. Я также пробовал с ключевыми словами async/await и без них в методе getRequest. В любом случае моя собственность теперь возвращается как undefined. Не уверен, что это шаг в правильном направлении или нет.

import cheerio = require('cheerio');
import request = require('request-promise');

export class Address {
    public html;
    public $: CheerioStatic;

    constructor() {
        this.getHtml();

        // this.parseHtml();
    }

    private async makeRequest() {
        const options = {
            uri: 'https://www.google.com/',
            transform(body) {
                return cheerio.load(body);
            }
        };
        return await request(options);
    }

    private async getHtml() {
        this.html = await this.makeRequest();
    }
}

Обновление 2

Итак, не зная, что делать, я решил снова попробовать маршрут обещаний с модулем запроса вместо запроса-обещания. В моей функции makeRequest() я возвращаю новое обещание, которое упаковывает мой запрос, а затем вызывает для него .then() в методе getHtml(). Еще одна вещь, которую следует отметить, это то, что я тестирую этот код с помощью модульных тестов мокко. Не уверен, что это как-то связано. Я пытался сделать тест асинхронным и использовать там ожидание, но не сигару. Ниже мой класс и тест.

import request = require('request');

export class Address {
    public html;

    constructor() {
        this.html = this.getHtml();
    }

    public getHtml() {
        this.makeRequest().then((body) => {
            console.log(body);
            return body;
        });
    }

    private makeRequest() {
        return new Promise(function(resolve, reject) {
            request('https://www.google.com/', function(error, response, body) {
                if (error) {
                    reject(error);
                }
                resolve(body);
            });
        });
    }
}

Последнее наблюдение. Я вставил console.log(body); в метод getHtml(), чтобы увидеть, вызывается ли он. Когда я запускаю модульный тест и ставлю точку останова в любом месте теста, он никогда не вызывается, хотя я создал экземпляр своего класса. Однако, когда я продолжаю выполнение и заканчиваю тест, он распечатывает весь HTML! Так что мне кажется, что самый последний код в основном в порядке, но, возможно, есть какая-то проблема с синхронизацией. Поскольку HTML-код все еще распечатывается, вызов, по крайней мере, выполняется, но не доходит до моей собственности. Ниже приведен тест, который я выполняю.

describe('Address', () => {
    // const address = new Address();
    it('is not empty', () => {
        const address = new Address();
        const ad = address.html;
        // console.log(ad);
    });
});

Кроме того, в тесте я попытался сделать оператор it асинхронным и добавить await в address.html (также пытался дождаться создания экземпляра), и снова без сигары.


person Jand    schedule 27.08.2018    source источник


Ответы (3)


Наконец-то я нашел решение! По сути, я сделал асинхронную функцию, которая вызывает метод запроса, а затем должен был выполнить асинхронный тест, ожидающий вызова моего асинхронного метода. Подводя итог, кажется, что мне нужно создать метод для создания всего и не могу сделать это в конструкторе. Если кто-то может пролить свет на то, почему это так, это было бы здорово, поскольку мне это кажется действительно нелогичным. Я имею в виду, разве не для этого нужен конструктор? Может быть, я отправлю еще один вопрос об этом, если я не могу найти его нигде. Во всяком случае, мой код ниже.

import request = require('request');

export class Address {
    public html;

    constructor() {
        //
    }

    public async getHtml() {
        return await this.makeRequest()
            .then((body) => {
                this.html = body;
            });
    }

    public makeRequest() {
        return new Promise(function(resolve, reject) {
            request('https://fakena.me/random-real-address/', function(error, response, body) {
                if (error) {
                    reject(error);
                }
                resolve(body);
            });
        });
    }
}

И вот мой тестовый метод.

describe('Address', () => {
    // const address = new Address();
    it('is not empty', async () => {
        const address = new Address();
        await address.getHtml();
        console.log(address.html);
    });
});
person Jand    schedule 29.08.2018

когда я устанавливаю переменную this.html, вызывая метод this.makeRequest(), я возвращаю Promise {ожидание}. Я не уверен, почему это так.

Потому что async функции возвращают обещание.

Исправить

Переместите асинхронные функции вызова из других асинхронных функций (не конструктора) и используйте ожидание.

person basarat    schedule 27.08.2018
comment
Спасибо за быстрый ответ. Сделав то, что вы предложили, я теперь получаю undefined. Я обновил свой вопрос с вашим предложением. - person Jand; 28.08.2018

Мне непонятно, что ваш класс пытается смоделировать. Хотя пара замечаний. Если ваш класс моделирует какое-то значение и пытается скрыть, как это значение получено/создано внутри его частных функций, он должен сообщить своим потребителям, что это значение «асинхронно». Это означает, что хотя ваша программа компилируется, потому что this.html назначается внутри асинхронной функции, она фактически недоступна до определенного события (разрешение запроса в этом примере). Потребитель, с точки зрения типа, не может знать об этом.

Решением было бы просто назначить обещание this.html, например

class Adress {
  html: Promise<theRealTypeOfHtml>
  ...
  constructor () {
      this.getHtml();
  }
  // no more async
  private getHtml () {
    this.html = this.makeRequest()
  }
}

Используя это, любой потребитель теперь знает, что при создании экземпляра этого класса он возвращает объект, который имеет поле html, содержащее обещание, разрешающее то, что вы хотите, чтобы html был. На практике это означает, что любому потребителю этого класса будет настоятельно рекомендовано использовать его в контексте async:

async function main () {
  const anAdress = new Adress();
  const html = await anAdress.html;
}

В заключение вы только что подняли асинхронный контекст на уровне потребителя, а не на уровне производителя.

person adz5A    schedule 28.08.2018
comment
По сути, я пытаюсь сделать запрос на HTML-код веб-сайта (не Google). Затем я проанализирую свой HTML-код, чтобы получить адрес с помощью Cheerio. Я пропустил некоторые свойства, чтобы упростить код. Я предположил, что изменение имени моего класса могло бы быть полезным. В конечном итоге пользователь не будет иметь доступа к свойству this.html, но пока я просто пытаюсь заставить свой код работать, прежде чем слишком сильно беспокоиться об этом. У меня именно такая ситуация работает на С#, поэтому я знаю, что это возможно. Однако использование ваших предложений не изменило мой результат. Я все еще получаю возврат undefined. - person Jand; 28.08.2018
comment
Я отредактировал с помощью правильного конструктора, в нем больше нет присваивания, присваивание this.html выполняется в getHtml с запросом обещания в качестве значения. - person adz5A; 29.08.2018