Правильное использование промисов с Await/Async

У меня возникли некоторые проблемы с пониманием того, как работает функциональность Promise. Раньше я использовал Bluebird, но хотел попробовать изучить новый стандарт await/async, чтобы стать лучше как программист. Я использовал async/await и создал обещания там, где считаю нужным, однако функции по-прежнему выполняются не по порядку.

Я запускаю это в последней версии Node с Webpack, я не получаю никаких значимых ошибок. Он работает нормально, просто не так, как ожидалось. Мой вывод при запуске:

Searching the Web for: Test String
Web search Completed!
Promise { <pending> }
Response Handler Completed!

В идеале я хотел бы, чтобы он ответил:

Searching the Web for: Test String
Response Handler Completed
Web search Completed

А затем верните вывод моего обработчика ответов.

Может ли кто-нибудь заметить мою ошибку?

const https = require('https');

// Replace the subscriptionKey string value with your valid subscription key.
const subscriptionKey = '<samplekey>';

const host = 'api.cognitive.microsoft.com';
const path = '/bing/v7.0/search';

const response_handler = async (response) => {
    return new Promise((resolve, reject) => {
      let body = '';
      response.on('data', (d) => {
        body += d;
        resolve(body);
      });
      response.on('end', () => {
        console.log('\nRelevant Headers:\n');
        for (const header in response.headers)
                // header keys are lower-cased by Node.js
          {
          if (header.startsWith('bingapis-') || header.startsWith('x-msedge-')) { console.log(`${header}: ${response.headers[header]}`); }
        }
        body = JSON.stringify(JSON.parse(body), null, '  ');
        //console.log('\nJSON Test Response:\n');
        //console.log(body);
      });
      response.on('error', (e) => {
        console.log(`Error: ${e.message}`);
      });
      console.log('Response Handler Completed!');

    });
};

const bing_web_search = async (search) => {
  return new Promise((resolve, reject) => {
  console.log(`Searching the Web for: ${search}`);
  const request_params = {
    method: 'GET',
    hostname: host,
    path: `${path}?q=${encodeURIComponent(search)}&$responseFilter=${encodeURIComponent('Webpages')}&count=${50}`,
    headers: {
      'Ocp-Apim-Subscription-Key': subscriptionKey,
    },
  };

  const req = https.request(request_params, response_handler);

  console.log('Web search Completed!');
  console.log(req.body);
  req.end();
  });
};

module.exports = {
  search: async (search) => {
    if (subscriptionKey.length === 32) {
       const result = await bing_web_search(search);
       console.log('Search Completed');
    } else {
      console.log('Invalid Bing Search API subscription key!');
      console.log('Please paste yours into the source code.');
    }
  },
};

person Crafty    schedule 04.12.2017    source источник
comment
Нет смысла возвращать промис из асинхронной функции. Тогда он вообще не должен быть асинхронным. И ты никогда не звонишь resolve   -  person Jonas Wilms    schedule 04.12.2017
comment
Кроме того, вы должны отклонить() при ошибке!   -  person falsarella    schedule 04.12.2017
comment
Возможно, использование fetch API было бы проще, оно возвращает обещание и работает примерно так же, как $.ajax: developer.mozilla.org/en-US/docs/Web/API/Fetch_API   -  person HMR    schedule 04.12.2017
comment
Я собираюсь попробовать использовать fetch, а не https, похоже, по этому модулю не так много документации. Спасибо, парни!   -  person Crafty    schedule 04.12.2017
comment
Обещания являются частью ES2015 (ES6), а async/await — частью ES2017.   -  person Felix Kling    schedule 08.12.2017


Ответы (1)


Немного поздно, но следующее должно направить вас в путь, я внес изменения в код. Если у вас есть какие-либо вопросы, пожалуйста, дайте мне знать.

const https = require('https');

// Replace the subscriptionKey string value with your valid subscription key.
const subscriptionKey = '<samplekey>';

const host = 'api.cognitive.microsoft.com';
const path = '/bing/v7.0/search';

const response_handler = (resolve,reject) => (response) => { // no need for async, you return a promise
  //this one does not return anything, it's the handler for the response and will resolve
  // or reject accordingly
  let body = '';
  response.on('data', (d) => {
    body += d;
    //cannot resolve yet, we're not done
    //  you can resolve on end maybe? I don't know nodejs http
    //  if end event is called when request fails then end would not
    //  be the correct way either, better use fetch api
    //resolve(body);
  });
  response.on('end', () => {
    console.log('\nRelevant Headers:\n');
    for (const header in response.headers)
    // header keys are lower-cased by Node.js
    {
      if (header.startsWith('bingapis-') || header.startsWith('x-msedge-')) { console.log(`${header}: ${response.headers[header]}`); }
    }
    body = JSON.stringify(JSON.parse(body), null, '  ');
    resolve(body);//resolving the promise returned by bing_web_search
    //console.log('\nJSON Test Response:\n');
    //console.log(body);
  });
  response.on('error', (e) => {
    console.log(`Error: ${e.message}`);
    //need to reject with the error
    reject(e);
  });
  console.log('Response Handler Completed!');

};
//no need to specify async, you are not awaiting anything
//  you are creating a promise, when using non promise asynchronous
//  functions that work with callbacks or event emitting objects
//  you need resolve and reject functions so you have to return
//  new Promise(
//    (resolve,reject)=>somecallbackNeedingFunction((err,result)=>
//      err ? reject(err) : resolve(result)
//    )
//  )
const bing_web_search = (search) => {
  return new Promise((resolve, reject) => {
    console.log(`Searching the Web for: ${search}`);
    const request_params = {
      method: 'GET',
      hostname: host,
      path: `${path}?q=${encodeURIComponent(search)}&$responseFilter=${encodeURIComponent('Webpages')}&count=${50}`,
      headers: {
        'Ocp-Apim-Subscription-Key': subscriptionKey,
      },
    };

    const req = https.request(
      request_params, 
      response_handler(resolve,reject)//passing this resolve and reject
    );
    //no, request not completed, we just started
    console.log('Web search Completed!');
    // console.log(req.body); // nothing to log here
    req.end();
  });
};

module.exports = {
  search: async (search) => {
    if (subscriptionKey.length === 32) {
      //did not change anything bing_web_search returns a promise
      //  so you can just await it
      const result = await bing_web_search(search);
      console.log('Search Completed');
      //this will resolve with the results
      return result
    } else {
      console.log('Invalid Bing Search API subscription key!');
      console.log('Please paste yours into the source code.');
      //the caller of this function can handle the rejection
      return Promise.reject('Invalid Bing Search API subscription key!');
    }
  },
};

[обновление]

Ваш комментарий предполагает, что вы неправильно вызываете поиск или правильно обрабатываете обещание, которое он возвращает. Вы не можете контролировать, сколько времени занимает ответ, поэтому в наборе ответов первый запрос может вернуться последним. Вот почему у вас есть Promise.all

const searchObjects = [s1,s2];
const Fail = function(reason){this.reason=reason;};
Promise.all(
  searchObjects.map(
    searchObject => obj.search(searchObject)
    .then(
      x=>[x,searchObject]//if resolve just pass result
      ,err =>new Fail([err,searchObject])//if reject add a Fail object with some detail
    )
  )
)
.then(
  results => {
    console.log(
      "resolved results:",
      results.filter(([r,_])=>(r&&r.constructor)!==Fail)
    );
    console.log(
      "failed results:",
      results.filter(([r,_])=>(r&&r.constructor)===Fail)
    );
  }
)

Если у вас много поисковых запросов, возможно, вы захотите ограничить количество ответов с помощью определенного период времени или активные соединения. Дайте мне знать, если вам нужна помощь с этим.

person HMR    schedule 04.12.2017
comment
Привет, HMR, это не решило порядок ответов, но комментарии были действительно полезны для моего собственного понимания, поэтому я ценю это! Я попробую перейти с https на выборку, чтобы посмотреть, упростит ли это ситуацию. - person Crafty; 04.12.2017
comment
Если порядок нескольких запросов важен, вы можете сопоставить массив объектов поиска с обещаниями с помощью поиска и использовать Promise.all (обновленный ответ с примером) - person HMR; 05.12.2017