Ответы пользователя по тегу JavaScript
  • Nigtmare.js разъясните поведение метода evaluate?

    @likemusic
    Пример рабочего кода:
    const Nightmare = require('nightmare');
    const nightmare = Nightmare({ show: true });
    let results = [];
    let res = nightmare
        .goto('http://yahoo.com')
        .type('form[action*="/search"] [name=p]', 'github nightmare')
        .click('form[action*="/search"] [type=submit]')
        .wait('#main')
        .evaluate(function () {
            return document.querySelector('#main .searchCenterMiddle li a').href;
        })
        .then(function(yahooResult) {
            console.log('Yahoo result:', yahooResult);
            results.push(yahooResult);
        })
        .then(function () {
            return nightmare.goto('http://google.com')
                .type('form[action*="/search"] [name=q]', 'github nightmare')
                .click('form[action*="/search"] [type=submit]')
                .wait('#main')
                .evaluate(function () {
                    return document.querySelector('#search .g h3 a').href;
                })
            ;
        })
        .then(function (googleResult) {
            console.log('Google result:', googleResult);
            results.push(googleResult);
        })
        .then(function () {
            return nightmare.end();
        })
        .then(function() {
            console.log('Full results:', results);
        })
        .catch(function (error) {
            console.error('Search failed:', error);
        })
    ;

    Комментарии к коду:

    не совсем понятно, как в одном вызове можно использовать evaluate несколько раз и тем более в цикле, и можно ли вообще так действовать.


    Почти все методы nightmare возвращают указатель на самого себя, чтобы вызовы методов можно было объединять в цепочки (как в jQuery).
    Метод `evaluate()` также возвращает this (а не promise, как многие думают)!

    Методами-исключениями, которые не возвращают `this` являются методы `.then()` и `.catch()` - они оба возвращают Promise.
    Сами Promise также имеют методы `.then()` и `catch()`, поэтому при написании скриптом надо четко понимать и следить на объекте Promise или NightmareJS вызываются последующие методы.

    Также nightmarejs при вызове цепочечных методов по факту просто добавляет нужные действия в буфер комманд. Команды из буфер начинают выполняться только при вызове методов `.then()`, и `.catch()`.

    Поэтому код вроде приведенного ниже вообще даже не запустит браузер и не выполнит ничего, т.к. в нем нет вызова методов возвращающих промисы.

    Код который даже не запустит браузер
    nightmare
        .goto(url1).click(selector1).evaluate(func1)
        .goto(url2).click(selector1).evaluate(func1)
        .end()



    Но следующий код будет выполнен браузером:
    Код который будет выполнен в браузере
    nightmare
        .goto(url1).click(selector1).evaluate(func1)
        .goto(url2).click(selector1).evaluate(func1)
        .end()
        .catch(errorHandler)


    Также надо иметь в виду, что если функция вызываемае в `.thеn()` методе возвращает promise, то этот promise также будет разрешаться/выполняться и результат его работы будет возвращемым значением передаваемым во второй `.then()` верхнего уровня.

    Пример кода со вложенными промисами (https://jsfiddle.net/5kp62v7w/)
    promise1 = Promise.resolve('Promise 1')
    	.then(function(){ var str = 'Promise 1-1'; console.log(str); return str;})
    	.then(function(){ var str = 'Promise 1-2'; console.log(str); return str;})
    	.then(function(){ var str = 'Promise 1-3'; console.log(str); return str;})
    	.then(function(){ 
    		return Promise.resolve('Promise 2')
    			.then(function(){ var str = 'Promise 2-1'; console.log(str); return str;})
    			.then(function(){ var str = 'Promise 2-2'; console.log(str); return str;})
    			.then(function(){ var str = 'Promise 2-3'; console.log(str); return str;})
    			.then(function(){
    				return Promise.resolve('Promise 3')
    					.then(function(){ var str = 'Promise 3-1'; console.log(str); return str;})
    					.then(function(){ var str = 'Promise 3-2'; console.log(str); return str;})
    					.then(function(){ var str = 'Promise 3-3'; console.log(str); return str;})
    				;
    			})
    		;
    	})
    ;
    
    promise1
    	.then(function(res) {console.log('Result value:', res );})
    ;

    Результат выполнения кода
    Promise 1-1
    Promise 1-2
    Promise 1-3
    Promise 2-1
    Promise 2-2
    Promise 2-3
    Promise 3-1
    Promise 3-2
    Promise 3-3

    Result value: Promise 3-3


    Поэтому при последовательном получении данных код должен быть такой структуры:
    Пример кода при многократном вызове метода `.evaluate()`
    nightmare.goto(url1).click(selector1).evaluate(func1).then(saveHandler1)
      .then(
         return nightmare.goto(url2).click(selector2).evaluate(func2).then(saveHandler2)
      )
      .then(
         return nightmare.goto(url3).click(selector3).evaluate(func3).then(saveHandler3)
      )
      .then(
         return nightmare.end()
      )
      .catch(errorHandler)
    ;


    Циклы можно реализовать через reduce() метод, но на данный момент мне кажется проще всего писать с использованием await/async функций.

    Пример использования `.evaluate()` в цикле с помощью async/await-функций
    var urls = ['http://example.com', 'http://example2.com', 'http://example3.com'];
    
    var results = []
    async function collectTitles(urls){
      for ( url of urls ) {
        results.push(await nightmare.goto(url).wait('body').title())
      }
      
      await nightmare.end()
      console.dir(results)
    }
    
    collectTitles(urls)

    до вызова nightmare можно объявить любую переменную, к примеру var arr = [] и потом пытаться пушить в нее через evaluate


    Так сделать нельзя. Код передаваемый в метод `.evaluate()` работает в окружении браузера, а не node-сервера, поэтому единственных способ передать данные из кода выполняемого в браузе в код выполняемый на nodejs это вернуть из браузерной ф-ции значение с помощью оператора return и использовать затем это значение внутри `.than()` ф-ции как аргумент функции:

    Пример передачи значения из браузер в выполняемый скрипт
    var nodeArray = [];
    
    nightmare
       .goto(url)
       .evaluate(function(){
    })
       .then(function(){ var ret = []; /* do some work with ret */ return ret;  }) //т.к. этот код выполняется в окружении браузера, то у него нет доступа к переменным объявkенным в окружении nodejs, в том числе и к nodeArray. Значение `ret` будет передано первым аргументом в последющей then()-ф-ции
       .then(function(browserRet){ nodeArray = nodeArray.concat(browserRet);  }) // browserRet равен значению ret вычисленному в браузере, но переменная ret для окружения сервера не определена.


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

    Также рекомендую ознакомиться с примерами кода и комментариями к ним в репозитории rosshinkley/nightmare-examples
    Ответ написан