variable not assigning inside promise

2019-04-09 09:00发布

问题:

Maybe I'm sleepy, but under what circumstances would the following occur?

let foo;

page
  .evaluate(() => {
    // return works... but not closure assignment

    // doesn't work
    foo = 'foo';

    // works
    return 'bar';
  })
  .then(bar => {
    console.log('foobar', foo, bar);
    // > foobar undefined bar
  });

This is occurring in a mocha test using puppeteer

update: the exact entire code

node 9.11.2

/* global describe, it, before, after */

const fs = require('fs-extra');
const path = require('path');
const assert = require('assert');
const puppeteer = require('puppeteer');
const sleep = require('shleep');

const extPath = path.resolve(__dirname, '..', 'build');
const { name } = fs.readJSONSync(path.resolve(extPath, 'manifest.json'));

// Access chrome object in Extensions
// https://github.com/GoogleChrome/puppeteer/issues/2878

describe('chrome extension', () => {
  let browser;
  let extensionPage;

  before(async function() {
    this.timeout(90 * 1000);

    // start puppeteer
    browser = await puppeteer.launch({
      headless: false,
      args: [
        `--disable-extensions-except=${extPath}`,
        `--load-extension=${extPath}`
      ]
    });

    // poll instead of hope this is enough time?
    const EXT_LOAD_DELAY = 100;
    await sleep(EXT_LOAD_DELAY);

    const targets = await browser.targets();

    const extensionTarget = targets.find(
      ({ _targetInfo }) =>
        _targetInfo.type === 'background_page' && _targetInfo.title === name
    );

    const page = await extensionTarget.page();

    let foo;

    page
      .evaluate(() => {
        // return works... but not closure assignment

        // doesn't work
        foo = 'foo';

        // doesn't log
        console.log('foo', foo);

        // works
        return 'bar';
      })
      .then(bar => {
        console.log('foobar', foo, bar);
        // > foobar undefined bar
      });
  });

  it('should load', async () => {
    assert(true);
  });
});

screenshot of test

回答1:

The evaluate method in puppeteer has no concept of variables that you have declared in your code either locally or globally. The function that you pass to the evaluate method are functions that are to be executed in the page context (ie. in your browser). Since foo is not declared in the page context, it cannot access it and therefore cannot update its value.

So to step through your code:

let foo;

await page.evaluate(() => {
  foo = 'foo';  // Since no variable foo is known to the evaluate method in the context of your page
                // this line , effectively, assigns a new variable called foo with a value 'foo' which
                // is then lost once the method has completed since its value is never returned.

  return 'bar'; // This value is returned from the method and is therefore retained below
})
.then(bar => {
  console.log('foobar', foo, bar);
  // foobar is output as expected since you've hardcoded it

  // foo is now referring to the global foo you declared earlier but since you have used `let` and not
  // assigned it any value, it is output as 'undefined'

  // bar is the value you returned from your 'evaluate' function and is therefore output as 'bar' as
  // expected in the console log.
});

If you wanted to use the evaluate to update your variable foo you would have to do it like this:

let foo;
foo = await page.evaluate(() => {
  return 'foo'
});
console.log(foo); // Now outputs 'foo' as expected

You can, however, inject variables into evaluate methods and update their values (if you wish), for example:

let foo = 'foo'
console.log(foo); // Outputs 'foo' as expected
foo = await page.evaluate((injectedFoo) => {
  return `new${injectedFoo}`;
}, foo);
console.log(foo); // Outputs 'newfoo'

So what happens here is you've injected the variable foo into the evaluate method by passing it as an argument at the end of the method declaration. The evaluate method now contains a variable (which I've called injectedFoo for clarity) which carries the original value of the foo variable.

I'm then returning the string new appended to the beginning to the foo variable string and output the final value of that in the console.

I hope this helps to explain how the evaluate method works!