4

I'm having some issues understanding how the Promise functionality works, I have previously used Bluebird but I wanted to try to learn the new await/async standard in order to improve as a programmer. I have used async/await and created promises where I feel appropriate however the functions are still executing out of order.

I'm running this on the latest version of Node with Webpack, I'm not getting any meaningful errors. It runs fine just not as expected. My output when running it is:

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

Ideally I'd like it to respond with:

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

And then return the output of my response handler.

Can anyone spot my mistake?

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.');
    }
  },
};
Felix Kling
  • 795,719
  • 175
  • 1,089
  • 1,143
Crafty
  • 41
  • 2
  • 3
    There is no sense in returning a promise from an async function. Then it does not need to be async at all. And you never call `resolve` – Jonas Wilms Dec 04 '17 at 17:33
  • Also, you should reject() on error! – falsarella Dec 04 '17 at 17:37
  • Maybe using the fetch api would be simpler, it returns a promise and works a bit like `$.ajax`: https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API – HMR Dec 04 '17 at 17:49
  • I'm going to try and use fetch rather than https, there doesn't seem to be that much documentation on that module. Thanks Guys! – Crafty Dec 04 '17 at 19:47
  • Promises are part of ES2015 (ES6) and `async/await` is part of ES2017. – Felix Kling Dec 08 '17 at 15:14

1 Answers1

0

A bit late but the following should set you on the way, I made changes to the code. If you have any questions please let me know.

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!');
    }
  },
};

[update]

Your comment suggest that you do not call search correctly or handle the promise it returns correctly. You have no control over how long a response takes so in a set of responses the first request may return last. This is why you have 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)
    );
  }
)

If you have a lot of searches then maybe you want to throttle the amount of responses withing a certain time period or active connections. Let me know if you need help with that.

HMR
  • 37,593
  • 24
  • 91
  • 160
  • Hi HMR, this didn't resolve the ordering of the responses but the comments have been really helpful for my own understanding so I appreciate that! I'll try migrating from https to fetch to see if that simplifies things a touch. – Crafty Dec 04 '17 at 19:45
  • If the order of multiple requests is important you could map an array of search objects to promises using search and use `Promise.all` (updated answer with example) – HMR Dec 05 '17 at 03:19