return 在 Puppeteer/Node.js 中对变量声明语句 (return a = 2;) 的混淆

Confusion around returning a variable declaration statement (return a = 2;) in Puppeteer/Node.js

我正在尝试学习 Node.js 和一些 await/promise,我遇到了这个例子 (https://remarkablemark.org/blog/2018/04/15/puppeteer-without-async-await/):

const puppeteer = require('puppeteer');

let _browser;
let _page;

puppeteer
    .launch()
    .then(browser => (_browser = browser))
    .then(browser => (_page = browser.newPage()))
    .then(page => page.goto('https://example.com'))
    .then(() => _page)
    .then(page => page.screenshot({ path: 'example.png' }))
    .then(() => _browser.close());

我理解 .then(browser => (_browser = browser)).then(function(browser) { return _browser = browser; }) 相同,但我有点困惑为什么有些行使用 _browser 而有些行使用 browser(与_page).

我也不知道 .then(() => _page) 行是干什么用的。为什么这一行没有一个参数,它是前一个函数返回的参数,即 page.goto(...)?我可能不太了解 Puppeteer 的 .then()

我正在尝试清理这段代码并使用 promises 和箭头函数:

puppeteer
    .launch()
    .then(function(browser) {
        return browser.newPage();
    })
    .then(function(page) {
        return page.goto(url).then(function() {
            return page.content();
        });
    })
    .then(function(html) {
       // do something
    })
    .catch(function(err) {
        //handle error
    });

请注意以下事项:

  • var a; var b = a = 2;。这将相同的值分配给变量 ab,因为 a = 2 将值 2 分配给变量 a 和 returns 分配的值是 2 然后分配给变量 b.
  • (params?) => returnvalue(params?) => { return returnvalue; }function(params?) { return returnvalue; } 将是相同的函数定义。然而,编译器在几种情况下识别这些函数定义中的 this 关键字的方式发生了一些变化,在这种情况下这不是一个需要关注的问题。
  • Promise.then return再次承诺 return Promise or value or default undefined.

解释:

let _browser;
let _page;
puppeteer
    .launch()
    .then(browser => (_browser = browser))
    .then(browser => (_page = browser.newPage()))
    .then(page => page.goto('https://example.com'))
    // >>> Above .then may not be returning a page, so below .then will replace it with globally referenced _page
    .then(() => _page)
    .then(page => page.screenshot({ path: 'example.png' }))
    // >>> Need a browser reference to close here
    .then(() => _browser.close());

为了在整个承诺链中跟踪所需的 browserpage 引用,开发人员已在全局变量中分配引用并在整个过程中使用它们。

下面是建议的方法,以便在整个承诺链中清理和维护 [browser, page] 的引用。

puppeteer
    .launch()
    .then(browser => browser.newPage().then(page => [browser, page]))
    .then(([browser, page]) => page.goto('https://example.com').then(() => [browser, page]))
    .then(([browser, page]) => page.screenshot({ path: 'example.png' }).then(() => [browser, page]))
    .then(([browser, page]) => browser.close());

如果您使用 asyncawait,代码会显得更清晰。

puppeteer.launch() returns 以 browser 解析的承诺,可在 then ,

_browser 是局部变量,它将 browser 赋值给它,因为在随后的所有 .then 中携带 browser 会很麻烦,例如, 你在最后看到的_browser.close() 假设用browser.close() 关闭原来的browser,但是browser 不可用,这就是为什么你需要将它存储在变量中所以你可以做到 _browser.close()

_pagepage

相同的故事

这是使用 async/await 的相同行为:

(async () => {
  const browser = await puppeteer.launch();  
  const page = await browser.newPage();  
  await page.goto('https://example.com');
  page.screenshot({ path: 'example.png' })
  await browser.close()
})()

当谈到 .then(() => _page) 时,其目的是使 page 在下一个 .then 中可用,请参阅下面的代码段,其中 5 返回并可用下一个 .then

const wait = (time) => new Promise((res, rej) => setTimeout(() => res("hello"), time));

let _someVar;

wait(1000)
  .then(msg => (_someVar = msg)) // this is .then(_browser => (_browser = browser))
  .then(() => 5) // this is .then(() => _page)
  .then(x => console.log(x));

console.log("a : ", _someVar);

setTimeout(() => {
  console.log("b : ", _someVar);
}, 2000);

那是因为 page.goto() returns :

Promise which resolves to the main resource response. In case of multiple redirects, the navigation will resolve with the response of the last redirect.

() => _page 如果 page.goto 返回 page 则不需要

() => _page ,尽管我认为它可以省略并在下一个 _page.screenshot() 中执行 .then

当您为变量赋值时,该赋值的 return 值就是被赋值的值。例如:

let x;
const two = (x = 2); // x = 2 returns '2'
console.log(two); // 2 (return value of x = 2)
console.log(x); // 2 (x is set above to equal 2)

接下来,Promises 和 .then() 有一些需要注意的重要事项:

  1. 首先,.then() 将 return 一个可以解析为值的新 Promise。 Promise 解析到的值可以使用 .then(resolvedValue => ...).

    获得
  2. 如前所述,.then() return是一个可以解析为值的 Promise。 Promise 解析到的值由您传递给 .then() 函数 return 的函数决定。例如,如果您有 .then(() => xyz),那么此 .then() 方法调用的 Promise return 将是 Promise<xyz>。这是说明上面第 1 点和第 2 点的示例:

const promise = Promise.resolve(); // Promise<> (a promise that resolves to the value of undefined/nothing)

const promiseAbc = promise.then(() => 'abc'); // returns Promise<'abc'> (a Promise that resolves to 'abc')
promiseAbc.then(abc => console.log(abc)); // "extract" the resolved value from promiseAbc) (logs: 'abc')

  1. 如第 2 点所述,回调到 .then() 的 returned 值被 .then() 用作 Promise returned 的解析值称呼。但是,当您 return 来自 .then() 回调的 Promise 时,这会略有不同。 return 不是 return 解析为 Promise 的新 Promise,.then() 方法 return 是您从 .then() 回调中 return 编辑的 Promise。这是一个清除此问题的示例:

const promise = Promise.resolve(); // Promise<> (a promise that resolves to the value of undefined/nothing)
const promiseAbc = Promise.resolve('abc'); // Promise<'abc'> (a promise that resolves to the value of 'abc')

const newPromise = promise.then(() => promiseAbc);
// You might expect that the above would return and set `newPromise` to be:
// Promise<Promise<'abc'>>
// But, as `promiseAbc` is a Promise, we return that instead, and so the above actually sets `newPromise` to be:
// Promise<'abc'>
newPromise.then(abc => console.log(abc)); // get the resolved value from `newPromise` and log it. We see it is 'abc', and not Promise<'abc'>

因此,下面的代码做了一些事情:

​.then(browser => (_browser = browser))
  1. 上面的代码会将 _browser 变量设置为 browser 值,该值来自 .launch()[= 解析的 Promise returned 49=]

  2. 箭头函数将 return 值 browser(因为 _browser = browser 将计算为 browser

  3. .then() 将 return 一个新的 Promise,解析为 browser 值。发生这种情况是因为您从 .then() 的箭头函数中 return 的值变成了 return 由 .then() 编辑的 Promise 的解析值(参见上面关于.then 的 intracies。这意味着 紧接着 .then() 调用 return 的 Promise 解析之后的 .then() 调用至 browser 将能够在其箭头功能中访问它。

你的代码保存 browser 的值和由 browser.newPage() 编辑的 Promise return 在变量中的原因是它可以稍后在你的 Promise 链中访问它们在任意点。

请参阅代码注释以了解您的代码评估过程的说明:

// Promise<xyz> means that the Promise resolves to `xyz`, 
// which you can access by using `.then(xyz => ...)` on the  Promise
puppeteer
  .launch() // returns Promise<browser> <---------------------|
  .then(browser => (_browser = browser)) // sets _browser = browser, returns Promise<browser>
  .then(browser => (_page = browser.newPage())) // sets _page = browser.newPage() (_page is now a Promise), returns `Promise<page>`,  which is the promise returned `browser.newPage()` (see  point 3 of `.then()` intracies above) 
  .then(page => page.goto('https://example.com')) // gets `page` from the previously returned Promise. This returns Promise<HTTPResponse> (as page.goto() returns Promise<HTTPResponse>) - this return value is ignored, as we don't need to use the `HTTPResponse` 
  .then(() => _page) // returns the `_page` Promise, this is done so the next `.then()` can access the resolved value of the `_page` Promise. (see point 3 of the above `.then()` intracies)
  .then(page => page.screenshot({ path: 'example.png' })) // get the `page` value (which is the resolve value of the `_page` promise, returned by the above `.then()`), and return `Promise<Buffer>` - this value is ignored as it is not used in the next `.then()` call
  .then(() => _browser.close()); // return Promise<BrowserContext>

与其执行所有这些操作,不如使用 async/await,如 docs 中所示,这样更容易理解:

const puppeteer = require('puppeteer');
(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.goto('https://example.com');
  await page.screenshot({path: 'screenshot.png'});
  await browser.close();
})();