2017-12-18 9 views
0

最近、私はphantomjs-nodeライブラリを試しています。私が達成したいことは、基本的に動的なWebページテンプレートを作成し、phantomjs-nodeライブラリを使用して "実行"し、最後にレンダリングされたページからデータを抽出することでした。Node.js&co - 約束とイベントコールバックの混同を避ける

:最も簡単な設定で

は、このアプローチする最初の試みは、(以下の例では、テンプレートは、それにもかかわらず、それは原則的に外部ライブラリを利用するいくつかの更なるロジック等が含まれているかもしれません、ただ静的である)、このように見えました page.evaluateによって返された約束の then -callbackが最終的に呼び出されたときに、メインファントム・プロセスがすでに持っているので

12/18/2017, 2:44:32 PM - info: start 
12/18/2017, 2:44:33 PM - info: done 
12/18/2017, 2:44:33 PM - info: onLoadFinished 
12/18/2017, 2:44:33 PM - error: Phantom process stopped with exit code 0 

最も可能性が高い:

var phantom = require('phantom'); 
var co = require('co'); 
var sleep = require('system-sleep'); 
var winston = require('winston'); 

const logger = new winston.Logger({ 
    level: 'debug', 
    transports: [new winston.transports.Console({ 
     json: false, timestamp:() => (new Date()).toLocaleString() 
    })] 
}); 

co(function*() { 
    logger.info('start'); 
    var instance = yield phantom.create(); 
    try { 
     const html = ` 
       <!DOCTYPE html> 
       <html> 
        <head> 
         <title>Page title</title> 
        </head> 
        <body> 
         <div id='results'>Page data</div> 
        </body> 
       </html> 
      `; 

     var page = yield instance.createPage();  

     yield page.on('onLoadFinished', function(){ 
      logger.info('onLoadFinished'); 

      page.evaluate(function(){ 
       return document.getElementById('results').textContent;  
      }).then(function(val){ 
       logger.info(`RESULT = ${val}`);  
      }).catch(function(val){ 
       logger.error(val.message);  
      }); 
     }); 

     yield page.setContent(html, 'http://localhost'); 

    }catch (e){ 
     logger.error(e.message);  
    }finally{ 
     instance.exit(); 
    } 
    logger.info('done'); 
}); 

しかし、これは出力で失敗します終了しました。

これを「修正」するためには、私は(下記の例の残りの部分を省略)は、次の即興戦略に頼っ:

var page = yield instance.createPage(); 

    var resolver; 
    var P = new Promise(function(resolve, reject){ resolver = resolve; }); 

    yield page.on('onLoadFinished', function(){ 
     logger.info('onLoadFinished'); 

     resolver(page.evaluate(function(){ 
      return document.getElementById('results').textContent; 
     })); 
    }); 

    yield page.setContent(html, 'http://localhost'); 

    const val = yield P; 
    logger.info(`RESULT = ${val}`); 

これは、本質的に「外部」で解決された新しい約束を作成するには約束はpage.evaluateから返されました。 coブロックその後、ブロックの終わりにyield P文で必要な結果が準備できるまで、期待通りので、出力は次のとおりです。

12/18/2017, 2:53:47 PM - info: start 
12/18/2017, 2:53:48 PM - info: onLoadFinished 
12/18/2017, 2:53:48 PM - info: RESULT = ..... 
12/18/2017, 2:53:48 PM - info: done 

これが動作しているようですが、例えば、例外がスローされ、それは(かなり「ハック」と感じますコールバックではresolverの呼び出しがメインのtry/catchブロックで検出されないため)コールバックからcoで管理されているコールバックに制御を「転送」するには、よりクリーンなアプローチが必要なのでしょうか?

答えて

2
  • co +ジェネレータ関数を使用しないでください。 async/awaitはこちら
  • はい、すべて(最大でも)約束事に1回だけイベントコールバックを変換する必要があります。
  • いいえ、そのような約束事を作成せず、「外部的に解決してください」。約束のコンストラクタの中にそれらを解決するものを置くだけです。

(async function() { 
    logger.info('start'); 
    var instance = await phantom.create(); 
    try { 
     const html = `…`; 
     const page = await instance.createPage();  

     await new Promise((resolve, reject) => { 
      page.on('loadFinished', resolve); 
      page.on('resourceError', reject); // or something like that? 
      page.setContent(html, 'http://localhost'); // this doesn't appear to return a promise 
     }) 
     logger.info('onLoadFinished'); 

     try { // this extra inner try looks superfluous 
      const val = await page.evaluate(function(){ 
       return document.getElementById('results').textContent;  
      }); 
      logger.info(`RESULT = ${val}`); 
     } catch(e) { 
      logger.error(e.message);  
     } 
    } catch(e) { 
     logger.error(e.message);  
    } finally { 
     instance.exit(); 
    } 
    logger.info('done'); 
}()); 
+0

詳細なコメントをありがとう! 'Promise'コンストラクタ内に別の' async'関数をネストする必要がありますか?私が理解する限り、 'page.on'呼び出しは非同期です(原則的に別の約束を返します)。もし実際には' page.setContent'が 'loadFinished'コールバックが実行される前に実行され、終了します。セット? – ewcz

+0

@ewcz No. ['Promise'コンストラクタに' async関数 'を渡さないでください。](https://stackoverflow.com/a/43083793/1048572)! 'page.on'は約束を返すようではないので、それを(それを約束するために)ラップしているのです。また、* callback *は非同期です。つまり、 'setContent' call(https://stackoverflow.com/a/31099819/1048572)の後に実行されます(これは、それが原因です。前)。 – Bergi

+0

私は、コンテンツが設定された状態でコールバック自体が開始されたことを理解しています。私は[source](https:// github)を理解する限り、 'page.on'を介してコールバックを割り当てる/基本的に[実行](https://github.com/amir20/phantomjs-node/blob/master/src/phantom.js)を呼び出しています。 )関数は約束を返します( 'page.on'の戻り値をコンソールに出力した場合、' Promise {} ') – ewcz

関連する問題