正确使用带有 Await/Async 的 Promise

Posted

技术标签:

【中文标题】正确使用带有 Await/Async 的 Promise【英文标题】:Using Promises with Await/Async Correctly 【发布时间】:2018-05-18 05:44:53 【问题描述】:

我在理解 Promise 功能的工作原理时遇到了一些问题,我以前使用过 Bluebird,但我想尝试学习新的 await/async 标准,以便提高程序员的水平。我使用了 async/await 并在我认为合适的地方创建了 Promise,但是这些功能仍然无序执行。

我正在使用 Webpack 在最新版本的 Node 上运行它,我没有收到任何有意义的错误。它运行良好,只是没有像预期的那样。我运行时的输出是:

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

【问题讨论】:

从异步函数返回承诺是没有意义的。然后它根本不需要异步。而且你永远不会打电话给resolve 另外,你应该在错误时拒绝()! 也许使用 fetch api 会更简单,它会返回一个 promise 并且有点像 $.ajax: developer.mozilla.org/en-US/docs/Web/API/Fetch_API 我将尝试使用 fetch 而不是 https,该模块上似乎没有那么多文档。谢谢各位! Promises 是 ES2015 (ES6) 的一部分,async/await 是 ES2017 的一部分。 【参考方案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)
    );
  
)

如果您有很多搜索,那么您可能希望使用某个 time period 或 active connections 来限制响应数量。如果您需要帮助,请告诉我。

【讨论】:

嗨 HMR,这并没有解决响应的排序问题,但是 cmets 对我自己的理解非常有帮助,所以我很感激!我会尝试从 https 迁移到 fetch 看看这是否简化了一些事情。 如果多个请求的顺序很重要,您可以使用搜索将一组搜索对象映射到承诺并使用 Promise.all(更新后的答案示例)

以上是关于正确使用带有 Await/Async 的 Promise的主要内容,如果未能解决你的问题,请参考以下文章

While 循环使用 Await Async。

async / await 的串行和并行

js异步回调Async/Await与Promise区别 新学习使用Async/Await

异步操作要了解的ES7的async/await

简单理解JavaScript 的async/await

js异步回调Async/Await与Promise区别