流程调节,必知必会
分类:前端技术

初探 Headless Chrome

2017/06/15 · 幼功本领 · Chrome

初藳出处: 饿了么前端   

现代 JS 流程序调节制:从回调函数到 Promises 再到 Async/Await

2018/09/03 · JavaScript · Promises

原稿出处: Craig Buckler   译文出处:OFED   

JavaScript 平时被认为是异步的。那代表怎么样?对开荒有何样影响呢?近日,它又生出了哪些的转换?

拜访以下代码:

result1 = doSomething1(); result2 = doSomething2(result1);

1
2
result1 = doSomething1();
result2 = doSomething2(result1);

超越1/3编制程序语言同步实践每行代码。第后生可畏行推行实现重回叁个结实。无论第后生可畏行代码推行多短时间,唯有进行到位第二行代码才会实践。

姚丽冰    学号:16050120089

Promise 异步流程序调整制

2017/10/04 · JavaScript · Promise

原著出处: 麦子谷   

什么是 Headless Chrome

Headless Chrome 是 Chrome 浏览器的无分界面形态,能够在不张开浏览器的前提下,使用全体 Chrome 协助的表征运营你的顺序。相比于今世浏览器,Headless Chrome 特别有帮忙测验web 应用,得到网址的截图,做爬虫抓取新闻等。相比于出道较早的 PhantomJS,SlimerJS 等,Headless Chrome 则更进一层贴近浏览器境况。

单线程处理程序

JavaScript 是单线程的。当浏览器选项卡执行脚本时,别的具备操作都会终止。这是自然的,因为对页面 DOM 的改观无法并发实行;三个线程
重定向 ULX570L 的同时,另三个线程正要增添子节点,这么做是朝不虑夕的。

客户不轻巧觉察,因为管理程序会以组块的样式快捷试行。举个例子,JavaScript 检验到按键点击,运转总结,并更新 DOM。大器晚成旦形成,浏览器就足以轻松管理队列中的下一个门类。

(附注: 别的语言比方 PHP 也是单线程,不过透过八十九线程的服务器比方 Apache 管理。同风度翩翩 PHP 页面同时提倡的多少个乞求,可以运维五个线程运行,它们是互相隔绝的 PHP 实例。)

原来的文章链接 zhuanlan.zhihu.com

图片 1前言

新近部门在招前端,作为机构唯风流浪漫的前端,面试了好多应聘的同学,面试中有一个关联 Promise 的多个主题材料是:

网页中预加载20张图片能源,分步加载,叁回加载10张,五回到位,怎么调节图片央浼的现身,怎么样感知当前异步央求是还是不是已到位?

而是能整个答上的非常少,能够交给叁个回调 计数版本的,作者都觉着合格了。那么接下去就一头来学习计算一下基于 Promise 来管理异步的两种艺术。

本文的例证是二个十二万分简化的二个卡通阅读器,用4张漫画图的加载来介绍异步管理差别措施的实现和差异,以下是 HTML 代码:

JavaScript

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Promise</title> <style> .pics{ width: 300px; margin: 0 auto; } .pics img{ display: block; width: 100%; } .loading{ text-align: center; font-size: 14px; color: #111; } </style> </head> <body> <div class="wrap"> <div class="loading">正在加载...</div> <div class="pics"> </div> </div> <script> </script> </body> </html>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Promise</title>
  <style>
    .pics{
      width: 300px;
      margin: 0 auto;
    }
    .pics img{
      display: block;
      width: 100%;
    }
    .loading{
      text-align: center;
      font-size: 14px;
      color: #111;
    }
  </style>
</head>
<body>
  <div class="wrap">
    <div class="loading">正在加载...</div>
    <div class="pics">
    </div>
  </div>
  <script>
  </script>
</body>
</html>

怎样拿到 Headless Chrome

目前,Mac 上 Chrome 59 beta 版本与 Linux 上的 Chrome 57 已经初始扶助 headless 个性。Windows 上 Chrome 一时半刻不扶植,能够采用 Chrome Canary 60 进行开垦。

通过回调达成异步

单线程发生了多个难点。当 JavaScript 履行叁个“缓慢”的管理程序,例如浏览器中的 Ajax 央求也许服务器上的数据库操作时,会时有发生哪些?那个操作可能须要几分钟 – 以致几分钟。浏览器在守候响合时会被锁定。在服务器上,Node.js 应用将不能够管理别的的客户乞请。

缓慢解决方案是异步管理。当结果就绪时,一个进度应诉知调用另三个函数,并非等待实现。那称之为回调,它看作参数字传送递给此外异步函数。举例:

doSomethingAsync(callback1卡塔尔(قطر‎; console.log('finished'卡塔尔(قطر‎; // 当 doSomethingAsync 达成时调用 function callback1(error卡塔尔(英语:State of Qatar) { if (!error) console.log('doSomethingAsync complete'); }

1
2
3
4
5
6
7
doSomethingAsync(callback1);
console.log('finished');
 
// 当 doSomethingAsync 完成时调用
function callback1(error) {
  if (!error) console.log('doSomethingAsync complete');
}

doSomethingAsync() 采纳回调函数作为参数(只传递该函数的引用,因而支付十分的小卡塔尔(英语:State of Qatar)。doSomethingAsync() 执行多久并不首要;大家所驾驭的是,callback1() 就要今后有个别时刻实施。调控台将展现:

finished doSomethingAsync complete

1
2
finished
doSomethingAsync complete

【嵌牛导读】Promise 想必大家都格外熟稔,想一想就那么多少个api,可是你真正精晓 Promise 吗?

单后生可畏央浼

最简易的,正是将异步四个个来管理,转为一个好像同步的点子来拍卖。 先来大致的贯彻二个单个 Image 来加载的 thenable 函数和几个管理函数重回结果的函数。

JavaScript

function loadImg (url) { return new Promise((resolve, reject) => { const img = new Image() img.onload = function () { resolve(img) } img.onerror = reject img.src = url }) }

1
2
3
4
5
6
7
8
9
10
function loadImg (url) {
  return new Promise((resolve, reject) => {
    const img = new Image()
    img.onload = function () {
      resolve(img)
    }
    img.onerror = reject
    img.src = url
  })
}

异步转同步的消除思想是:当第一个 loadImg(urls[1]卡塔尔(قطر‎ 完毕后再调用 loadImg(urls[2]卡塔尔(قطر‎,依次往下。假设 loadImg(卡塔尔(英语:State of Qatar)是几个合办函数,那么很当然的想到用__循环__。

JavaScript

for (let i = 0; i < urls.length; i ) { loadImg(urls[i]) }

1
2
3
for (let i = 0; i < urls.length; i ) {
  loadImg(urls[i])
}

当 loadImg(卡塔尔(قطر‎ 为异步时,大家就只好用 Promise chain 来完成,最后变成这种办法的调用:

JavaScript

loadImg(urls[0]) .then(addToHtml) .then(()=>loadImg(urls[1])) .then(addToHtml) //... .then(()=>loadImg(urls[3])) .then(addToHtml)

1
2
3
4
5
6
7
loadImg(urls[0])
  .then(addToHtml)
  .then(()=>loadImg(urls[1]))
  .then(addToHtml)
  //...
  .then(()=>loadImg(urls[3]))
  .then(addToHtml)

这大家用三当中级变量来积累当前的 promise ,就如链表的游标同样,改革后的 for 循环代码如下:

JavaScript

let promise = Promise.resolve() for (let i = 0; i < urls.length; i ) { promise = promise .then(()=>loadImg(urls[i])) .then(addToHtml) }

1
2
3
4
5
6
let promise = Promise.resolve()
for (let i = 0; i < urls.length; i ) {
promise = promise
.then(()=>loadImg(urls[i]))
.then(addToHtml)
}

promise 变量就好像二个迭代器,不断指向最新的归来的 Promise,那我们就特别运用 reduce 来简化代码。

JavaScript

urls.reduce((promise, url) => { return promise .then(()=>loadImg(url)) .then(addToHtml) }, Promise.resolve())

1
2
3
4
5
urls.reduce((promise, url) => {
  return promise
    .then(()=>loadImg(url))
    .then(addToHtml)
}, Promise.resolve())

在前后相继设计中,是足以因此函数的__递归__来落到实处循环语句的。所以我们将地点的代码改成__递归__:

JavaScript

function syncLoad (index) { if (index >= urls.length) return loadImg(urls[index]).then(img => { // process img addToHtml(img) syncLoad (index 1) }) } // 调用 syncLoad(0)

1
2
3
4
5
6
7
8
9
10
11
function syncLoad (index) {
  if (index >= urls.length) return
      loadImg(urls[index]).then(img => {
      // process img
      addToHtml(img)
      syncLoad (index 1)
    })
}
 
// 调用
syncLoad(0)

好了一个粗略的异步转同步的兑现情势就已经完成,大家来测验一下。 那么些达成的总结版本现已落到实处没难题,然则最上面的正在加载还在,那我们怎么在函数外界知道这么些递归的甘休,并隐蔽掉那些DOM 呢?Promise.then(卡塔尔 相似重返的是 thenable 函数 大家只须求在 syncLoad 内部传递那条 Promise 链,直到最后的函数再次回到。

JavaScript

function syncLoad (index) { if (index >= urls.length) return Promise.resolve() return loadImg(urls[index]) .then(img => { addToHtml(img) return syncLoad (index 1) }) } // 调用 syncLoad(0) .then(() => { document.querySelector('.loading').style.display = 'none' })

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function syncLoad (index) {
  if (index >= urls.length) return Promise.resolve()
  return loadImg(urls[index])
    .then(img => {
      addToHtml(img)
      return syncLoad (index 1)
    })
}
 
// 调用
syncLoad(0)
  .then(() => {
  document.querySelector('.loading').style.display = 'none'
})

今昔我们再来康健一下那个函数,让它更是通用,它承担__异步函数__、异步函数必要的参数数组、__异步函数的回调函数__两个参数。而且会记录调用战败的参数,在最后回到到函数外界。其它大家能够考虑一下为啥catch 要在终极的 then 在此以前。

JavaScript

function syncLoad (fn, arr, handler) { if (typeof fn !== 'function'卡塔尔throw TypeError('第3个参数必需是function'卡塔尔(英语:State of Qatar) if (!Array.isArray(arr卡塔尔国)throw TypeError('第一个参数必须是数组'卡塔尔(英语:State of Qatar) handler = typeof fn === 'function' ? handler : function (卡塔尔(英语:State of Qatar) {} const errors = [] return load(0) function load (index) { if (index >= arr.length) { return errors.length > 0 ? Promise.reject(errors) : Promise.resolve() } return fn(arr[index]) .then(data => { handler(data) }) .catch(err => { console.log(err) errors.push(arr[index]) return load(index 1) }) .then(() => { return load (index 1) }) } } // 调用 syncLoad(loadImg, urls, addToHtml) .then(() => { document.querySelector('.loading').style.display = 'none' }) .catch(console.log)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
function syncLoad (fn, arr, handler) {
  if (typeof fn !== 'function') throw TypeError('第一个参数必须是function')
  if (!Array.isArray(arr)) throw TypeError('第二个参数必须是数组')
  handler = typeof fn === 'function' ? handler : function () {}
  const errors = []
  return load(0)
  function load (index) {
    if (index >= arr.length) {
      return errors.length > 0 ? Promise.reject(errors) : Promise.resolve()
    }
    return fn(arr[index])
      .then(data => {
        handler(data)
      })
      .catch(err => {
        console.log(err)              
        errors.push(arr[index])
        return load(index 1)
      })
      .then(() => {
        return load (index 1)
      })
  }
}
 
// 调用
syncLoad(loadImg, urls, addToHtml)
  .then(() => {
    document.querySelector('.loading').style.display = 'none'
  })
  .catch(console.log)

demo1地址:单纯性伏乞 – 八个 Promise 同步化

于今甘休,那么些函数仍有挺多不通用的主题素材,比方:管理函数必需后生可畏致,不可能是二种差别的异步函数组成的种类,异步的回调函数也只可以是一种等。关于这种办法的更详尽的陈诉能够看本人以前写的意气风发篇小说 Koa引用库之Koa-compose。

道理当然是那样的这种异步转同步的办法在此二个例子中并非最好的解法,但当有适用的政工场景的时候,那是很普及的实施方案。

如何在终极中利用

在Mac上选拔前,提出先绑定 Chrome 的外号

JavaScript

alias google-chrome="/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"

1
alias google-chrome="/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"

Linux下没有必要绑定小名,从官互连网下载最新版 Chrome 之后一向运转以下命令就可以。

接下来,在顶峰中输入:

google-chrome --headless --disable-gpu --remote-debugging-port=9222

1
google-chrome --headless --disable-gpu --remote-debugging-port=9222  https://github.com

日增 –disable-gpu 首假使为着挡住现阶段也许接触的荒谬。

这会儿,Headless Chrome已经打响运转了。展开浏览器,输入 http://localhost:9222,你拜候到如下的分界面:图片 2

在终端中,大家还是能做以下操作:

得到荧屏截图:

JavaScript

google-chrome --headless --disable-gpu --screenshot --window-size=1280,1696

1
google-chrome --headless --disable-gpu --screenshot --window-size=1280,1696 https://github.com

获取页面为PDF:

JavaScript

google-chrome --headless --disable-gpu --print-to-pdf

1
google-chrome --headless --disable-gpu --print-to-pdf https://github.com

打字与印刷页面DOM:

JavaScript

google-chrome --headless --disable-gpu --dump-dom

1
google-chrome --headless --disable-gpu --dump-dom https://github.com/

回调鬼世界

不足为道,回调只由一个异步函数调用。由此,能够行使轻巧、佚名的内联函数:

doSomethingAsync(error => { if (!error) console.log('doSomethingAsync complete'); });

1
2
3
doSomethingAsync(error => {
  if (!error) console.log('doSomethingAsync complete');
});

生机勃勃多种的四个或越来越多异步调用能够透过嵌套回调函数来延续产生。比如:

async1((err, res) => { if (!err) async2(res, (err, res) => { if (!err) async3(res, (err, res) => { console.log('async1, async2, async3 complete.'); }); }); });

1
2
3
4
5
6
7
async1((err, res) => {
  if (!err) async2(res, (err, res) => {
    if (!err) async3(res, (err, res) => {
      console.log('async1, async2, async3 complete.');
    });
  });
});

噩运的是,那引进了回调鬼世界 —— 叁个臭名远扬的定义,以致有特意的网页介绍!代码很难读,并且在增多错误管理逻辑时变得更糟。

回调鬼世界在顾客端编码中相对少见。要是你调用 Ajax 央浼、更新 DOM 并等待动画完结,恐怕需求嵌套两到三层,可是平日还算可拘禁。

操作系统或服务器进度的境况就分化了。二个 Node.js API 可以收起文件上传,更新几个数据库表,写入日志,并在发送响应在此之前行行下一步的 API 调用。

【嵌牛鼻子】:Promise

并发须求

聊到底同一域名下能够并发两个 HTTP 央浼,对于这种无需按顺序加载,只要求按梯次来处理的产出须要,Promise.all 是最佳的解决办法。因为Promise.all 是原生函数,大家就引述文书档案来解释一下。

Promise.all(iterable卡塔尔(英语:State of Qatar) 方法指当全体在可迭代参数中的 promises 已到位,或然第多少个传递的 promise(指 reject)失败时,重回 promise。
出自 Promise.all() – JavaScript | MDN

那大家就把demo1中的例子改一下:

JavaScript

const promises = urls.map(loadImg卡塔尔 Promise.all(promises) .then(imgs => { imgs.forEach(addToHtml)document.querySelector('.loading'卡塔尔.style.display = 'none' }卡塔尔 .catch(err => { console.error(err, 'Promise.all 当当中二个并发谬误,就能reject。'卡塔尔(قطر‎ }卡塔尔(英语:State of Qatar)

1
2
3
4
5
6
7
8
9
const promises = urls.map(loadImg)
Promise.all(promises)
  .then(imgs => {
    imgs.forEach(addToHtml)
    document.querySelector('.loading').style.display = 'none'
  })
  .catch(err => {
    console.error(err, 'Promise.all 当其中一个出现错误,就会reject。')
  })

demo2地址:现身央求 – Promise.all

长间隔调控

在上文中描述的都选用终端命令运转 Headless Chrome,下文以博取截图为例,尝试什么在前后相继里决定 Headless Chrome。

设置重视

JavaScript

npm install lighthouse chrome-remote-interface --save

1
npm install lighthouse chrome-remote-interface --save

完成截图的大致思路为:通过行使 lighthouse 运营 Headless Chrome,然后经过 chrome-remote-interface 长途调节浏览器,使用 Page 监察和控制页面包车型客车加载,使用 Emulation 模块调度视口缩放,最后生成一张截图。

JavaScript

const { ChromeLauncher } = require('lighthouse/lighthouse-cli/chrome-launcher') const chrome = require('chrome-remote-interface') const fs = require('fs') const deviceMetrics = { width: 1200, height: 800, deviceScaleFactor: 0, mobile: false, fitWindow: false } const screenshotMetrics = { width: deviceMetrics.width, height: deviceMetrics.height } let protocol let launcher function launchChrome () { const launcher = new ChromeLauncher({ port: 9222, autoSelectChrome: true, additionalFlags: ['--window-size=412,732', '--disable-gpu', '--headless'] }卡塔尔(قطر‎ return launcher.run(卡塔尔.then((卡塔尔(قطر‎ => launcher卡塔尔 } function getScreenShot (卡塔尔国 { const { Page, Emulation } = protocol return Page.enable(卡塔尔 .then((卡塔尔 => { Emulation.setDeviceMetricsOverride(deviceMetrics卡塔尔国 // 配置浏览器尺寸 Emulation.setVisibleSize(screenshotMetrics卡塔尔国 // 配置截图尺寸 Page.navigate({ url: '' }) return new Promise((resolve, reject) => { Page.loadEventFired(() => { resolve(Page.captureScreenshot({ format: 'jpeg', fromSurface: true })) }) }) }) .then(image => { const buffer = new Buffer(image.data, 'base64') return new Promise((resolve, reject) => { fs.writeFile('output.jpeg', buffer, 'base64', err => { if (err) return reject(err) resolve() }) }) }) } launchChrome() .then(Launcher => { launcher = Launcher return new Promise((resolve, reject) =>{ chrome(Protocol => { protocol = Protocol resolve() }).on('error', err => { reject(err) }) }) }) .then(getScreenShot) .then(() => { protocol.close() launcher.kill() }) .catch(console.error)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
const { ChromeLauncher } = require('lighthouse/lighthouse-cli/chrome-launcher')
const chrome = require('chrome-remote-interface')
const fs = require('fs')
const deviceMetrics = {
  width: 1200,
  height: 800,
  deviceScaleFactor: 0,
  mobile: false,
  fitWindow: false
}
const screenshotMetrics = {
  width: deviceMetrics.width,
  height: deviceMetrics.height
}
let protocol
let launcher
 
function launchChrome () {
  const launcher = new ChromeLauncher({
    port: 9222,
    autoSelectChrome: true,
    additionalFlags: ['--window-size=412,732', '--disable-gpu', '--headless']
  })
  return launcher.run().then(() => launcher)
}
function getScreenShot () {
  const { Page, Emulation } = protocol
  return Page.enable()
    .then(() => {
      Emulation.setDeviceMetricsOverride(deviceMetrics) // 配置浏览器尺寸
      Emulation.setVisibleSize(screenshotMetrics) // 配置截图尺寸
      Page.navigate({ url: 'https://github.com/' })
      return new Promise((resolve, reject) => {
        Page.loadEventFired(() => {
          resolve(Page.captureScreenshot({ format: 'jpeg', fromSurface: true }))
        })
      })
    })
    .then(image => {
      const buffer = new Buffer(image.data, 'base64')
      return new Promise((resolve, reject) => {
        fs.writeFile('output.jpeg', buffer, 'base64', err => {
          if (err) return reject(err)
          resolve()
        })
      })
    })
}
launchChrome()
  .then(Launcher => {
    launcher = Launcher
    return new Promise((resolve, reject) =>{
      chrome(Protocol => {
        protocol = Protocol
        resolve()
      }).on('error', err => { reject(err) })
    })
  })
  .then(getScreenShot)
  .then(() => {
    protocol.close()
    launcher.kill()
  })
  .catch(console.error)

此处运用 lighthouse 提供的 ChromeLauncher 模块来调用 Chrome,假若Computer上安装了Chrome Canary,lighthouse 暗中同意会运维 Chrome Canary,能够将 autoSelectChrome 设置为false 然后活动接受使用什么版本。

经过 chrome-remote-interface 同盟 Headless Chrome,我们还是能做越多事情。

应用 CSS 和 DOM 模块,能够得到和安装页面中的 DOM 节点内容和 CSS 样式。

JavaScript

function getStyle () { const { Page, CSS, DOM } = protocol return Promise.all([ DOM.enable(), CSS.enable(), Page.enable() ]) .then(() => { Page.navigate({ url: '' }) return new Promise((resolve, _) => { Page.loadEventFired(() => { resolve(DOM.getDocument()) }) }) }) .then(res => res.root.nodeId) .then(nodeId => DOM.querySelector({ selector: '.btn-primary', nodeId })) .then(({ nodeId }) => CSS.getComputedStyleForNode({ nodeId })) .then(style => { console.log(style) }) }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function getStyle () {
  const { Page, CSS, DOM } = protocol
  return Promise.all([
      DOM.enable(),
      CSS.enable(),
      Page.enable()
    ])
    .then(() => {
      Page.navigate({ url: 'https://github.com/' })
      return new Promise((resolve, _) => {
        Page.loadEventFired(() => { resolve(DOM.getDocument()) })
      })
    })
    .then(res => res.root.nodeId)
    .then(nodeId => DOM.querySelector({ selector: '.btn-primary', nodeId }))
    .then(({ nodeId }) => CSS.getComputedStyleForNode({ nodeId }))
    .then(style => { console.log(style) })
}

行使 Runtime 模块,能够在页面运转时进行 JS 脚本。

JavaScript

function search () { const { Page, Runtime } = protocol return Promise.all([ Page.enable() ]) .then(() => { Page.navigate({ url: '' }) return new Promise((resolve, _) => { Page.loadEventFired(() => { resolve() }) }) }) .then(() => { const code = [ 'var input = document.querySelector('.s_ipt')', 'var btn = document.querySelector('#su')', 'input.value='123'' ].join(';') return Runtime.evaluate({ expression: code }) }) .then(() => { return new Promise((resolve, _) => { setTimeout(() => { resolve(Page.captureScreenshot({ format: 'jpeg', fromSurface: true })) }, 3000) }) }) .then(image => { const buffer = new Buffer(image.data, 'base64') return new Promise((resolve, reject) => { fs.writeFile('output.jpeg', buffer, 'base64', err => { if (err) return reject(err) resolve() }) }) }) }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
function search () {
  const { Page, Runtime } = protocol
  return Promise.all([
      Page.enable()
    ])
    .then(() => {
      Page.navigate({ url: 'https://www.baidu.com/' })
      return new Promise((resolve, _) => {
        Page.loadEventFired(() => { resolve() })
      })
    })
    .then(() => {
      const code = [
        'var input = document.querySelector('.s_ipt')',
        'var btn = document.querySelector('#su')',
        'input.value='123''
      ].join(';')
      return Runtime.evaluate({ expression: code })
    })
    .then(() => {
      return new Promise((resolve, _) => {
        setTimeout(() => {
          resolve(Page.captureScreenshot({ format: 'jpeg', fromSurface: true }))
        }, 3000)
      })
    })
    .then(image => {
      const buffer = new Buffer(image.data, 'base64')
      return new Promise((resolve, reject) => {
        fs.writeFile('output.jpeg', buffer, 'base64', err => {
          if (err) return reject(err)
          resolve()
        })
      })
    })
}

应用 Network 模块,能够读取并安装 UserAgent 和 库克ie 等音讯。

JavaScript

function setUAandCookie () { const { Page, Network } = protocol return Promise.all([ Network.enable(), Page.enable() ]) .then(() => { const userAgent = Network.setUserAgentOverride({ userAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.71 Safari/537.36" }) Network.setCookie({ url: '', name: 'test', value: '123', domain: '.github.com', path: '/', httpOnly: true }) Page.navigate({ url: '' }) return new Promise((resolve, _) => { Page.loadEventFired(() => { resolve() }) }) }) .then(() => { return Network.getCookies() }) .then(console.log) }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
function setUAandCookie () {
  const { Page, Network } = protocol
  return Promise.all([
      Network.enable(),
      Page.enable()
    ])
    .then(() => {
      const userAgent =
      Network.setUserAgentOverride({ userAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.71 Safari/537.36" })
      Network.setCookie({
        url: 'https://github.com',
        name: 'test',
        value: '123',
        domain: '.github.com',
        path: '/',
        httpOnly: true
      })
      Page.navigate({ url: 'https://github.com/' })
      return new Promise((resolve, _) => {
        Page.loadEventFired(() => { resolve() })
      })
    })
    .then(() => {
      return Network.getCookies()
    })
    .then(console.log)
}

Promises

ES2015(ES6) 引入了 Promises。回调函数仍有用,然则Promises 提供了更清晰的链式异步命令语法,由此得以串联运营(下个章节会讲)。

筹算依照 Promise 封装,异步回调函数必需回到二个 Promise 对象。Promise 对象会实行以下三个函数(作为参数字传送递的)个中之生机勃勃:

  • resolve:实践成功回调
  • reject:推行停业回调

以下例子,database API 提供了叁个 connect() 方法,选取贰个回调函数。外界的 asyncDBconnect() 函数立即重返了三个新的 Promise,大器晚成旦三回九转创设成功或退步,resolve()reject() 便会进行:

const db = require('database'卡塔尔国; // 连接数据库 function asyncDBconnect(param卡塔尔国 { return new Promise((resolve, reject卡塔尔国 => { db.connect(param, (err, connection卡塔尔(قطر‎ => { if (err卡塔尔(قطر‎ reject(err卡塔尔国; else resolve(connection卡塔尔; }卡塔尔(英语:State of Qatar); }卡塔尔(英语:State of Qatar); }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const db = require('database');
 
// 连接数据库
function asyncDBconnect(param) {
 
  return new Promise((resolve, reject) => {
 
    db.connect(param, (err, connection) => {
      if (err) reject(err);
      else resolve(connection);
    });
 
  });
 
}

Node.js 8.0 以上提供了 util.promisify() 功能,能够把根据回调的函数转变来基于 Promise 的。有八个利用口径:

  1. 流传叁个唯风姿洒脱的异步函数
  2. 流传的函数希望是错误优先的(譬如:(err, value卡塔尔(قطر‎ => …),error 参数在前,value 随后

举例:

// Node.js: 把 fs.readFile promise 化 const util = require('util'), fs = require('fs'), readFileAsync = util.promisify(fs.readFile); readFileAsync('file.txt');

1
2
3
4
5
6
7
// Node.js: 把 fs.readFile promise 化
const
  util = require('util'),
  fs = require('fs'),
  readFileAsync = util.promisify(fs.readFile);
 
readFileAsync('file.txt');

各个库都会提供温馨的 promisify 方法,寥寥几行也足以谐和撸一个:

// promisify 只采用一个函数参数 // 传入的函数接纳 (err, data卡塔尔 参数 function promisify(fn卡塔尔国 { return function(卡塔尔 { return new Promise( (resolve, reject卡塔尔(قطر‎ => fn( ...Array.from(arguments卡塔尔国, (err, data卡塔尔(قطر‎ => err ? reject(err卡塔尔 : resolve(data卡塔尔 卡塔尔(قطر‎ 卡塔尔; } } // 比方 function wait(time, callback卡塔尔(英语:State of Qatar) { setTimeout((卡塔尔 => { callback(null, 'done'卡塔尔(英语:State of Qatar); }, time卡塔尔(قطر‎; } const asyncWait = promisify(wait卡塔尔国; ayscWait(1000卡塔尔(英语:State of Qatar);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// promisify 只接收一个函数参数
// 传入的函数接收 (err, data) 参数
function promisify(fn) {
  return function() {
      return new Promise(
        (resolve, reject) => fn(
          ...Array.from(arguments),
        (err, data) => err ? reject(err) : resolve(data)
      )
    );
  }
}
 
// 举例
function wait(time, callback) {
  setTimeout(() => { callback(null, 'done'); }, time);
}
 
const asyncWait = promisify(wait);
 
ayscWait(1000);

【嵌牛提问】:本文遵照 Promise 的部分知识点总括了十道题,看看你能做对几道。

并发央求,按梯次管理结果

Promise.all 纵然能并发八个央浼,但是豆蔻梢头旦中间某八个 promise 出错,整个 promise 会被 reject 。 webapp 里常用的财富预加载,大概加载的是 20 张逐帧图片,当网络现身难题, 20 张图难免会有风流洒脱两张号召退步,如果失败后,直接舍弃其余被 resolve 的回到结果,就如有一点点不妥,大家只要领悟怎么图片出错了,把失误的图形再做二回呼吁或着用占位图补上就好。 上节中的代码 const promises = urls.map(loadImg)运营后,全体都图片恳求都已发出去了,大家假设按梯次依次管理 promises 那一个数组中的 Promise 实例就好了,先用二个简短点的 for 循环来贯彻以下,跟第一节中的单后生可畏乞请同样,利用 Promise 链来挨门逐户管理。

JavaScript

let task = Promise.resolve() for (let i = 0; i < promises.length; i ) { task = task.then(() => promises[i]).then(addToHtml) }

1
2
3
4
let task = Promise.resolve()
for (let i = 0; i < promises.length; i ) {
  task = task.then(() => promises[i]).then(addToHtml)
}

改成 reduce 版本

JavaScript

promises.reduce((task, imgPromise) => { return task.then(() => imgPromise).then(addToHtml) }, Promise.resolve())

1
2
3
promises.reduce((task, imgPromise) => {
  return task.then(() => imgPromise).then(addToHtml)
}, Promise.resolve())

demo3地址:Promise 并发央求,顺序处理结果

在 Karma 中央银行使 Headless Chrome 实行单元测量检验

对照于 PhantomJS 等,使用 Headless Chrome 做单元测量检验特别附近浏览器开垦情状。同期 PhantomJS 作者也生机勃勃度功遂身退,在 Chrome 公布 Headless 形式后,发布通知不再维护 PhantomJS 项目。

安装信任

JavaScript

npm install jasmine-core karma karma-chrome-launcher karma-jasmine -D

1
npm install jasmine-core karma karma-chrome-launcher karma-jasmine -D

配置 Karma

JavaScript

// karma.conf.js module.exports = function (config) { config.set({ frameworks: ['jasmine'], files: ['./test.js'], browsers: ["Chrome_Beta_Headless"], customLaunchers: { Chrome_Beta_Headless: { base: 'Chrome', flags: [ '--headless', '--disable-gpu', '--remote-debugging-port=9222' ] } }, browserConsoleLogOptions: { level: 'log', terminal: true }, reporters: ['progress'], autoWatch: false, singleRun: true }) }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// karma.conf.js
module.exports = function (config) {
  config.set({
    frameworks: ['jasmine'],
    files: ['./test.js'],
    browsers: ["Chrome_Beta_Headless"],
    customLaunchers: {
      Chrome_Beta_Headless: {
        base: 'Chrome',
        flags: [
          '--headless',
          '--disable-gpu',
          '--remote-debugging-port=9222'
        ]
      }
    },
    browserConsoleLogOptions: {
      level: 'log',
      terminal: true
    },
    reporters: ['progress'],
    autoWatch: false,
    singleRun: true
  })
}

编纂测量检验用例

JavaScript

// test.js describe('test', function() { it('should be true', function() { console.log(window.navigator.userAgent) expect(true).toEqual(true); }); });

1
2
3
4
5
6
7
// test.js
describe('test', function() {
  it('should be true', function() {
    console.log(window.navigator.userAgent)
    expect(true).toEqual(true);
  });
});

配置npm script

JavaScript

// package.json ... scripts: { test: "karma start" } ...

1
2
3
4
5
6
// package.json
...
scripts: {
  test: "karma start"
}
...

在终端中运作

JavaScript

npm test

1
npm test

结果图片 3

从重返结果中得以看到,测量检验已运行在 Headless Chrome 意况下。

异步链式调用

别的重临 Promise 的函数都可以经过 .then() 链式调用。前叁个 resolve 的结果会传送给后三个:

asyncDBconnect(''卡塔尔(قطر‎ .then(asyncGetSession卡塔尔(قطر‎ // 传递 asyncDBconnect 的结果 .then(asyncGetUser卡塔尔 // 传递 asyncGetSession 的结果 .then(asyncLogAccess卡塔尔国 // 传递 asyncGetUser 的结果 .then(result => { // 同步函数 console.log('complete'卡塔尔; // (传递 asyncLogAccess 的结果卡塔尔国return result; // (结果传给下两个 .then(卡塔尔(英语:State of Qatar)卡塔尔 }卡塔尔 .catch(err => { // 任何二个 reject 触发 console.log('error', err卡塔尔国; }卡塔尔国;

1
2
3
4
5
6
7
8
9
10
11
asyncDBconnect('http://localhost:1234')
  .then(asyncGetSession)      // 传递 asyncDBconnect 的结果
  .then(asyncGetUser)         // 传递 asyncGetSession 的结果
  .then(asyncLogAccess)       // 传递 asyncGetUser 的结果
  .then(result => {           // 同步函数
    console.log('complete');  //   (传递 asyncLogAccess 的结果)
    return result;            //   (结果传给下一个 .then())
  })
  .catch(err => {             // 任何一个 reject 触发
    console.log('error', err);
  });

一起函数也足以进行 .then(),再次来到的值传递给下四个 .then()(如果有)。

当别的四个前边的 reject 触发时,.catch() 函数会被调用。触发 reject 的函数后边的 .then() 也不再举办。贯穿整个链条能够存在多个 .catch() 方法,进而捕获不一致的谬误。

ES2018 引入了 .finally() 方法,它不管重临结果什么,都会试行最后逻辑 – 举个例子,清理操作,关闭数据库连接等等。当前只有 Chrome 和 Firefox 补助,不过 TC39 技委已经透露了 .finally() 补丁。

function doSomething(卡塔尔 { doSomething1(卡塔尔(英语:State of Qatar) .then(doSomething2卡塔尔(英语:State of Qatar).then(doSomething3卡塔尔国 .catch(err => { console.log(err卡塔尔国; }卡塔尔国 .finally((卡塔尔(قطر‎=> { // 清理操作放那儿! }卡塔尔(قطر‎; }

1
2
3
4
5
6
7
8
9
10
11
function doSomething() {
  doSomething1()
  .then(doSomething2)
  .then(doSomething3)
  .catch(err => {
    console.log(err);
  })
  .finally(() => {
    // 清理操作放这儿!
  });
}

【嵌牛正文】:以下 promise 均代表 Promise 实例,情状是 Node.js。

垄断最大并发数

现行反革命大家来试着成功一下地点的笔试题,那个实际都__没有必要调控最大并发数__。 20张图,分四遍加载,那用七个 Promise.all 不就减轻了?不过用 Promise.all不能侦听到每一张图片加载成功的风云。而用上生机勃勃节的法子,大家不只能并发诉求,又能按顺序响应图片加载成功的事件。

JavaScript

let index = 0 const step1 = [], step2 = [] while(index < 10) { step1.push(loadImg(`./images/pic/${index}.jpg`)) index = 1 } step1.reduce((task, imgPromise, i) => { return task .then(() => imgPromise) .then(() => { console.log(`第 ${i 1} 张图片加载达成.`卡塔尔(英语:State of Qatar) }卡塔尔(英语:State of Qatar) }, Promise.resolve(卡塔尔卡塔尔国 .then((卡塔尔国 => { console.log('>> 前边10张已经加载完!'卡塔尔(قطر‎ }卡塔尔(英语:State of Qatar) .then((卡塔尔(英语:State of Qatar) => { while(index < 20卡塔尔国 { step2.push(loadImg(`./images/pic/${index}.jpg`)) index = 1 } return step2.reduce((task, imgPromise, i) => { return task .then(() => imgPromise) .then(() => { console.log(`第 ${i 11} 张图片加载实现.`卡塔尔国 }卡塔尔国 }, Promise.resolve(卡塔尔(英语:State of Qatar)卡塔尔国 }卡塔尔 .then((卡塔尔国 => { console.log('>> 后边10张已经加载完'卡塔尔(قطر‎ }卡塔尔(قطر‎

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
let index = 0
const step1 = [], step2 = []
 
while(index < 10) {
  step1.push(loadImg(`./images/pic/${index}.jpg`))
  index = 1
}
 
step1.reduce((task, imgPromise, i) => {
  return task
    .then(() => imgPromise)
    .then(() => {
      console.log(`第 ${i 1} 张图片加载完成.`)
    })
}, Promise.resolve())
  .then(() => {
    console.log('>> 前面10张已经加载完!')
  })
  .then(() => {
    while(index < 20) {
      step2.push(loadImg(`./images/pic/${index}.jpg`))
      index = 1
    }
    return step2.reduce((task, imgPromise, i) => {
      return task
        .then(() => imgPromise)
        .then(() => {
          console.log(`第 ${i 11} 张图片加载完成.`)
        })
    }, Promise.resolve())
  })
  .then(() => {
    console.log('>> 后面10张已经加载完')
  })

地方的代码是针对难点的 hardcode ,即便笔试的时候能写出那几个,都曾经是非常科学了,不过并未一个人写出来,said…

demo4地址(看调整台和网络央求卡塔尔(英语:State of Qatar):Promise 分步加载 – 1

那正是说大家在虚幻一下代码,写叁个通用的艺术出来,那几个函数再次回到二个Promise,还是能持续管理整个都图片加载完后的异步回调。

JavaScript

function stepLoad (urls, handler, stepNum) { const createPromises = function (now, stepNum) { let last = Math.min(stepNum now, urls.length) return urls.slice(now, last).map(handler) } let step = Promise.resolve() for (let i = 0; i < urls.length; i = stepNum) { step = step .then(() => { let promises = createPromises(i, stepNum) return promises.reduce((task, imgPromise, index) => { return task .then(() => imgPromise) .then(() => { console.log(`第 ${index 1

  • i} 张图片加载完毕.`) }) }, Promise.resolve()) }) .then(() => { let current = Math.min(i stepNum, urls.length) console.log(`>> 总共${current}张已经加载完!`) }) } return step }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function stepLoad (urls, handler, stepNum) {
const createPromises = function (now, stepNum) {
    let last = Math.min(stepNum now, urls.length)
    return urls.slice(now, last).map(handler)
  }
  let step = Promise.resolve()
  for (let i = 0; i < urls.length; i = stepNum) {
    step = step
      .then(() => {
        let promises = createPromises(i, stepNum)
        return promises.reduce((task, imgPromise, index) => {
          return task
            .then(() => imgPromise)
            .then(() => {
              console.log(`第 ${index 1 i} 张图片加载完成.`)
            })
        }, Promise.resolve())
      })
      .then(() => {
        let current = Math.min(i stepNum, urls.length)
        console.log(`>> 总共${current}张已经加载完!`)
      })
  }
return step
}

上边代码里的 for 也足以改成 reduce ,但是要求先将要求加载的 urls 按分步的数额,划分成数组,感兴趣的心上人能够协和写写看。

demo5地址(看调整台和互连网诉求卡塔尔国:Promise 分步 – 2

但地点的贯彻和我们说的__最大并发数调整__没什么关联啊,最大并发数调控是指:当加载 20 张图片加载的时候,先并发供给 10 张图纸,当一张图纸加载成功后,又会三番五遍倡导一张图片的央浼,让并发数保持在 12个,直到须要加载的图形都全体倡导号令。这些在写爬虫中得以说是相比较分布的行使情状了。 那么大家根据地方的一些学问,大家用二种方法来贯彻这些职能。

小结

本文简要介绍了一下 Headless Chrome 在极限的用法,甚至怎么着使用 Headless Chrome 获取截图、获取页面中的CSS和DOM、设置UA和Cookie、运转JS脚本、配合Karma 举办单元测量检验。接下来,就等着您探究更加多关于 Headless Chrome 的用法了…

参考:

https://developers.google.com/web/updates/2017/04/headless-chrome
How to install and use Headless Chrome on OSX

1 赞 1 收藏 评论

图片 4

选取 Promise.all(卡塔尔国 管理三个异步操作

Promise .then() 方法用于各种实施的异步函数。即便不关怀顺序 – 举个例子,最初化不相干的构件 – 全数异步函数同时开动,直到最慢的函数执行 resolve,整个工艺流程甘休。

Promise.all() 适用于这种现象,它接收一个函数数组並且重回另二个Promise。举个例子:

Promise.all([ async1, async2, async3 ]卡塔尔国 .then(values => { // 重临值的数组 console.log(values卡塔尔国; // (与函数数组顺序风流倜傥致卡塔尔(قطر‎ return values; }卡塔尔(قطر‎ .catch(err => { // 任生龙活虎 reject 被触发 console.log('error', err卡塔尔(قطر‎; }卡塔尔(قطر‎;

1
2
3
4
5
6
7
8
Promise.all([ async1, async2, async3 ])
  .then(values => {           // 返回值的数组
    console.log(values);      // (与函数数组顺序一致)
    return values;
  })
  .catch(err => {             // 任一 reject 被触发
    console.log('error', err);
  });

专断三个异步函数 rejectPromise.all() 会立时终止。

题目一

接纳递归

黄金时代旦大家的最大并发数是 4 ,这种艺术的尤为重要寻思是一定于 4 个__纯净伏乞__的 Promise 异步职责在同不常候运行运转,4 个十足央浼不断递归取图片 U奥德赛L 数组中的 U福睿斯L 发起呼吁,直到 U帕杰罗L 全体取完,最终再接纳 Promise.all 来拍卖最后还在乞求中的异步义务,我们复用第4节__递归__本子的思绪来达成那个功用:

JavaScript

function limitLoad (urls, handler, limit) { const sequence = [].concat(urls卡塔尔 // 对数组做二个拷贝 let count = 0 const promises = [] const load = function () { if (sequence.length <= 0 || count > limit) return count = 1 console.log(`当下并发数: ${count}`) return handler(sequence.shift()) .catch(err => { console.error(err) }) .then(() => { count -= 1 console.log(`一时一刻并发数:${count}`) }) .then(() => load()) } for(let i = 0; i < limit && i < sequence.length; i ){ promises.push(load()) } return Promise.all(promises) }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function limitLoad (urls, handler, limit) {
  const sequence = [].concat(urls) // 对数组做一个拷贝
  let count = 0
  const promises = []
 
  const load = function () {
    if (sequence.length <= 0 || count > limit) return
    count = 1
    console.log(`当前并发数: ${count}`)
    return handler(sequence.shift())
      .catch(err => {
        console.error(err)
      })
      .then(() => {
        count -= 1
        console.log(`当前并发数:${count}`)
      })
      .then(() => load())
  }
 
  for(let i = 0; i < limit && i < sequence.length; i ){
    promises.push(load())
  }
  return Promise.all(promises)
}

设定最大央浼数为 5,Chrome 中呼吁加载的 timeline :图片 5

demo6地址(看调节台和网络央求卡塔尔:Promise 调整最大并发数 – 方法1

利用 Promise.race(卡塔尔(英语:State of Qatar) 管理三个异步操作

Promise.race()Promise.all() 极度相符,不一样之处在于,当第多个Promise resolve 或然 reject 时,它将会 resolve 大概reject。独有最快的异步函数会被实行:

Promise.race([ async1, async2, async3 ]) .then(value => { // 单一值 console.log(value); return value; }) .catch(err => { // 任一 reject 被触发 console.log('error', err); });

1
2
3
4
5
6
7
8
Promise.race([ async1, async2, async3 ])
  .then(value => {            // 单一值
    console.log(value);
    return value;
  })
  .catch(err => {             // 任一 reject 被触发
    console.log('error', err);
  });

const promise = new Promise((resolve, reject) => {

使用 Promise.race

Promise.race 选拔一个 Promise 数组,重临这一个数组中最早被 resolve 的 Promise 的再次来到值。终于找到 Promise.race 的选拔意况了,先来选择这些方法达成的遵守代码:

JavaScript

function limitLoad (urls, handler, limit) { const sequence = [].concat(urls卡塔尔(英语:State of Qatar) // 对数组做三个拷贝 let count = 0 let promises const wrapHandler = function (url卡塔尔 { const promise = handler(url卡塔尔国.then(img => { return { img, index: promise } }) return promise } //并发诉求到最大数 promises = sequence.splice(0, limit卡塔尔(英语:State of Qatar).map(url => { return wrapHandler(url卡塔尔(英语:State of Qatar) }卡塔尔 // limit 大于一切图纸数, 并发全部伸手 if (sequence.length <= 0卡塔尔 { return Promise.all(promises卡塔尔(قطر‎ } return sequence.reduce((last, url卡塔尔(قطر‎ => { return last.then((卡塔尔国 => { return Promise.race(promises卡塔尔(英语:State of Qatar) }卡塔尔(قطر‎.catch(err => { console.error(err卡塔尔(قطر‎}卡塔尔国.then((res卡塔尔(英语:State of Qatar) => { let pos = promises.findIndex(item => { return item == res.index }卡塔尔(英语:State of Qatar) promises.splice(pos, 1卡塔尔promises.push(wrapHandler(url卡塔尔国卡塔尔 }卡塔尔 }, Promise.resolve(卡塔尔卡塔尔.then((卡塔尔(英语:State of Qatar) => { return Promise.all(promises卡塔尔(英语:State of Qatar) }卡塔尔国 }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
function limitLoad (urls, handler, limit) {
  const sequence = [].concat(urls) // 对数组做一个拷贝
  let count = 0
  let promises
  const wrapHandler = function (url) {
    const promise = handler(url).then(img => {
      return { img, index: promise }
    })
    return promise
  }
  //并发请求到最大数
  promises = sequence.splice(0, limit).map(url => {
    return wrapHandler(url)
  })
  // limit 大于全部图片数, 并发全部请求
  if (sequence.length <= 0) {
    return Promise.all(promises)
  }
  return sequence.reduce((last, url) => {
    return last.then(() => {
      return Promise.race(promises)
    }).catch(err => {
      console.error(err)
    }).then((res) => {
      let pos = promises.findIndex(item => {
        return item == res.index
      })
      promises.splice(pos, 1)
      promises.push(wrapHandler(url))
    })
  }, Promise.resolve()).then(() => {
    return Promise.all(promises)
  })
}

设定最大哀求数为 5,Chrome 中号令加载的 timeline :图片 6

demo7地址(看调整台和网络央浼卡塔尔(قطر‎:Promise 调整最大并发数 – 方法2

在使用 Promise.race 完结这些效果,主倘若延绵不断的调用 Promise.race 来回到已经被 resolve 的任务,然后从 promises 中删掉那一个 Promise 对象,再投入一个新的 Promise,直到全数的 U奥迪Q3L 被取完,最终再采用Promise.all 来管理全部图片实现后的回调。

前程光明呢?

Promise 减弱了回调鬼世界,可是引进了其他的标题。

课程平时不提,整个 Promise 链条是异步的,大器晚成多种的 Promise 函数都得回来本身的 Promise 也许在最后的 .then().catch() 或者 .finally() 方法里面试行回调。

自家也鲜明:Promise 烦扰了自家十分久。语法看起来比回调要复杂,大多地点会出错,调节和测验也成难题。不过,学习幼功依旧很要紧滴。

延伸阅读:

  • MDN Promise documentation
  • JavaScript Promises: an Introduction
  • JavaScript Promises … In Wicked Detail
  • Promises for asynchronous programming

  console.log(1)

写在最后

因为职行业内部部多量利用 ES6 的语法,Koa 中的 await/async 又是 Promise 的语法糖,所以领会 Promise 种种流程调整是对自己来讲是可怜首要的。写的有不领会的地点和有不当的地点接待我们留言指正,其它还可能有其它没有涉嫌到的艺术也请我们提供一下新的不二等秘书诀和艺术。

Async/Await

Promise 看起来有个别复杂,所以 ES2017 引进了 asyncawait。就算只是语法糖,却使 Promise 尤其惠及,而且可避防止 .then() 链式调用的主题素材。看上面接纳 Promise 的例子:

function connect() { return new Promise((resolve, reject) => { asyncDBconnect(''卡塔尔 .then(asyncGetSession卡塔尔.then(asyncGetUser卡塔尔国 .then(asyncLogAccess卡塔尔(قطر‎ .then(result => resolve(result卡塔尔(قطر‎卡塔尔国 .catch(err => reject(err卡塔尔(英语:State of Qatar)卡塔尔 }卡塔尔(英语:State of Qatar); } // 运转 connect 方法 (自进行办法卡塔尔 ((卡塔尔(英语:State of Qatar) => { connect(卡塔尔(英语:State of Qatar); .then(result => console.log(result卡塔尔(英语:State of Qatar)卡塔尔国 .catch(err => console.log(err卡塔尔国卡塔尔 }卡塔尔(卡塔尔(英语:State of Qatar);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function connect() {
 
  return new Promise((resolve, reject) => {
 
    asyncDBconnect('http://localhost:1234')
      .then(asyncGetSession)
      .then(asyncGetUser)
      .then(asyncLogAccess)
      .then(result => resolve(result))
      .catch(err => reject(err))
 
  });
}
 
// 运行 connect 方法 (自执行方法)
(() => {
  connect();
    .then(result => console.log(result))
    .catch(err => console.log(err))
})();

使用 async / await 重写上面的代码:

  1. 外界方法用 async 声明
  2. 依照 Promise 的异步方法用 await 表明,能够确认保证下贰个指令试行前,它已推行到位

async function connect() { try { const connection = await asyncDBconnect(''卡塔尔(قطر‎, session = await asyncGetSession(connection卡塔尔(قطر‎, user = await asyncGetUser(session卡塔尔, log = await asyncLogAccess(user卡塔尔国; return log; } catch (e卡塔尔 { console.log('error', err卡塔尔国; return null; } } // 运营 connect 方法 (自举办异步函数卡塔尔国 (async (卡塔尔(英语:State of Qatar) => { await connect(卡塔尔(قطر‎; }卡塔尔(قطر‎(卡塔尔;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
async function connect() {
 
  try {
    const
      connection = await asyncDBconnect('http://localhost:1234'),
      session = await asyncGetSession(connection),
      user = await asyncGetUser(session),
      log = await asyncLogAccess(user);
 
    return log;
  }
  catch (e) {
    console.log('error', err);
    return null;
  }
 
}
 
// 运行 connect 方法 (自执行异步函数)
(async () => { await connect(); })();

await 使每个异步调用看起来疑似同步的,同期不推延 JavaScript 的单线程管理。别的,async 函数总是回到二个 Promise 对象,由此它可以被别的 async 函数调用。

async / await 恐怕不会让代码降少,然而有超级多独特之处:

  1. 语法更清楚。括号更少,出错的恐怕性也越来越小。
  2. 调治更便于。能够在此外 await 评释处设置断点。
  3. 错误管理尚佳。try / catch 能够与一头代码应用同风姿浪漫的管理方式。
  4. 支撑美好。全数浏览器(除了 IE 和 Opera Mini 卡塔尔国和 Node7.6 均已兑现。

如是说,未有完备的…

  resolve()

题外话

咱俩近些日子有 1 个前端的 HC,base 布Rees班,一家全部 50 架飞机的物流公司的AI部门,必要职业经验七年以上,那是公司社招供给的。 感兴趣的就联系自个儿吗,Email: d2hlYXLacrossevQGZveG1haWwuY29t

Promises, Promises

async / await 照旧依附 Promise 对象,最后借助回调。你供给通晓Promise 的专门的学业规律,它也并不意气风发致 Promise.all()Promise.race()。比较容易忽略的是 Promise.all(),这一个命令比选取风流浪漫体系非亲非故的 await 命令更迅捷。

  console.log(2)

参照他事他说加以考察资料

  • JavaScript Promise:简介 | Web | Google Developers
  • JavaScript Promise迷你书(中文版)

    1 赞 3 收藏 评论

图片 7

一起循环中的异步等待

或多或少意况下,你想要在后生可畏道循环中调用异步函数。举个例子:

async function process(array) { for (let i of array) { await doSomething(i); } }

1
2
3
4
5
async function process(array) {
  for (let i of array) {
    await doSomething(i);
  }
}

不起功能,下边包车型大巴代码也同样:

async function process(array) { array.forEach(async i => { await doSomething(i); }); }

1
2
3
4
5
async function process(array) {
  array.forEach(async i => {
    await doSomething(i);
  });
}

循环本中国人民保险公司持同步,並且总是在里面异步操作此前实现。

ES2018 引进异步迭代器,除了 next() 方法再次来到三个 Promise 对象之外,与平常迭代器近似。由此,await 关键字能够与 for ... of 循环一齐行使,以串行方式运转异步操作。比方:

async function process(array) { for await (let i of array) { doSomething(i); } }

1
2
3
4
5
async function process(array) {
  for await (let i of array) {
    doSomething(i);
  }
}

而是,在异步迭代器完结以前,最棒的方案是将数组每项 mapasync 函数,并用 Promise.all() 推行它们。比如:

const todo = ['a', 'b', 'c'], alltodo = todo.map(async (v, i) => { console.log('iteration', i); await processSomething(v); }); await Promise.all(alltodo);

1
2
3
4
5
6
7
8
const
  todo = ['a', 'b', 'c'],
  alltodo = todo.map(async (v, i) => {
    console.log('iteration', i);
    await processSomething(v);
});
 
await Promise.all(alltodo);

如此那般方便推行并行职责,不过不能将三次迭代结果传递给另一次迭代,並且映射大数组大概会花费总括质量。

})

丑陋的 try/catch

设若奉行停业的 await 未有包装 try / catchasync 函数将静默退出。假若有一长串异步 await 命令,须要八个 try / catch 包裹。

取代方案是采纳高阶函数来捕捉错误,不再必要 try / catch 了(感谢@wesbos的建议):

async function connect() { const connection = await asyncDBconnect(''卡塔尔, session = await asyncGetSession(connection卡塔尔国, user = await asyncGetUser(session卡塔尔(英语:State of Qatar), log = await asyncLogAccess(user卡塔尔(英语:State of Qatar); return true; } // 使用高阶函数捕获错误 function catchErrors(fn卡塔尔国 { return function (...args卡塔尔 { return fn(...args卡塔尔.catch(err => { console.log('ECR-VROHaval', err卡塔尔(英语:State of Qatar); }卡塔尔(英语:State of Qatar); } } (async (卡塔尔(قطر‎ => { await catchErrors(connect卡塔尔(英语:State of Qatar)(卡塔尔; }卡塔尔(卡塔尔;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
async function connect() {
 
  const
    connection = await asyncDBconnect('http://localhost:1234'),
    session = await asyncGetSession(connection),
    user = await asyncGetUser(session),
    log = await asyncLogAccess(user);
 
  return true;
}
 
// 使用高阶函数捕获错误
function catchErrors(fn) {
  return function (...args) {
    return fn(...args).catch(err => {
      console.log('ERROR', err);
    });
  }
}
 
(async () => {
  await catchErrors(connect)();
})();

当使用必得回到不相同于其余的失实时,这种作法就不太实用了。

固然有部分缺欠,async/await 依然 JavaScript 极度管用的互补。更加多能源:

  • MDN async 和 await
  • 异步函数 – 升高 Promise 的易用性
  • TC39 异步函数标准
  • 用异步函数简化异步编码

promise.then(() => {

JavaScript 之旅

异步编制程序是 JavaScript 无法幸免的挑战。回调在多数选用中是不可缺少的,不过轻巧陷入深度嵌套的函数中。

Promise 抽象了回调,不过有为数不菲句法陷阱。调换原来就有函数只怕是生龙活虎件苦差事,·then() 链式调用看起来很凌乱。

很幸运,async/await 表明清晰。代码看起来是联合的,但是又不独占单个管理线程。它将转移您书写 JavaScript 的法子,以至让你更侧重 Promise – 如若没接触过的话。

1 赞 收藏 评论

图片 8

  console.log(3)

})

console.log(4)

运维结果:

1

2

4

3

分解:Promise 构造函数是一齐推行的,promise.then 中的函数是异步试行的。

题目二

const promise1 = new Promise((resolve, reject) => {

  setTimeout(() => {

    resolve('success')

  }, 1000)

})

const promise2 = promise1.then(() => {

  throw new Error('error!!!')

})

console.log('promise1', promise1)

console.log('promise2', promise2)

setTimeout(() => {

  console.log('promise1', promise1)

  console.log('promise2', promise2)

}, 2000)

运维结果:

promise1 Promise { <pending> }

promise2 Promise { <pending> }

(node:50928) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: error!!!

(node:50928) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

promise1 Promise { 'success' }

promise2 Promise {

  <rejected> Error: error!!!

    at promise.then (...)

    at <anonymous> }

诠释:promise 有 3 种状态:pending、fulfilled 或 rejected。状态改换只好是 pending->fulfilled 可能pending->rejected,状态生龙活虎旦改动则不能够再变。上边 promise2 并不是promise1,而是重回的二个新的 Promise 实例。

题目三

const promise = new Promise((resolve, reject) => {

  resolve('success1')

  reject('error')

  resolve('success2')

})

promise

  .then((res) => {

    console.log('then: ', res)

  })

  .catch((err) => {

    console.log('catch: ', err)

  })

运作结果:

then: success1

分解:结构函数中的 resolve 或 reject 只有首先次实施有效,数次调用未有别的作用,呼应代码二结论:promise 状态生龙活虎旦退换则不可能再变。

题目四

Promise.resolve(1)

  .then((res) => {

    console.log(res)

    return 2

  })

  .catch((err) => {

    return 3

  })

  .then((res) => {

    console.log(res)

  })

运作结果:

1

2

解释:promise 能够链式调用。谈到链式调用大家普通会想到通过 return this 完成,可是 Promise 并非这么完结的。promise 每一次调用 .then 只怕 .catch 都会回去二个新的 promise,进而完成了链式调用。

题目五

const promise = new Promise((resolve, reject) => {

  setTimeout(() => {

    console.log('once')

    resolve('success')

  }, 1000)

})

const start = Date.now()

promise.then((res) => {

  console.log(res, Date.now() - start)

})

promise.then((res) => {

  console.log(res, Date.now() - start)

})

运作结果:

once

success 1005

success 1007

释疑:promise 的 .then 或然 .catch 能够被调用数十次,但此处 Promise 布局函数只进行一回。大概说 promise 内情黄金时代经济体改动,并且有了三个值,那么继续每便调用 .then 或然 .catch 都会间接得到该值。

题目六

Promise.resolve()

  .then(() => {

    return new Error('error!!!')

  })

  .then((res) => {

    console.log('then: ', res)

  })

  .catch((err) => {

    console.log('catch: ', err)

  })

运维结果:

then: Error: error!!!

    at Promise.resolve.then (...)

    at ...

疏解:.then 大概 .catch 中 return 四个 error 对象并不会抛出荒诞,所以不会被接续的 .catch 捕获,需求改成在这之中一种:

return Promise.reject(new Error('error!!!'))

throw new Error('error!!!')

因为重返任性三个非 promise 的值都会棉被服装进成 promise 对象,即 return new Error('error!!!') 等价于 return Promise.resolve(new Error('error!!!'))。

题目七

const promise = Promise.resolve()

  .then(() => {

    return promise

  })

promise.catch(console.error)

运作结果:

TypeError: Chaining cycle detected for promise #<Promise>

    at <anonymous>

    at process._tickCallback (internal/process/next_tick.js:188:7)

    at Function.Module.runMain (module.js:667:11)

    at startup (bootstrap_node.js:187:16)

    at bootstrap_node.js:607:3

释疑:.then 或 .catch 再次回到的值无法是 promise 本人,不然会促成死循环。相仿于:

process.nextTick(function tick () {

  console.log('tick')

  process.nextTick(tick)

})

题目八

Promise.resolve(1)

  .then(2)

  .then(Promise.resolve(3))

  .then(console.log)

运作结果:

1

释疑:.then 恐怕 .catch 的参数期望是函数,传入非函数则会发出值穿透。

题目九

Promise.resolve()

  .then(function success (res) {

    throw new Error('error')

  }, function fail1 (e) {

    console.error('fail1: ', e)

  })

  .catch(function fail2 (e) {

    console.error('fail2: ', e)

  })

运作结果:

fail2: Error: error

    at success (...)

    at ...

解说:.then 能够接过八个参数,第八个是拍卖成功的函数,第四个是管理错误的函数。.catch 是 .then 第四个参数的地利写法,然而它们用法上有一点点内需专一:.then 的第二个处理错误的函数捕获不了第叁个处理成功的函数抛出的荒诞,而一连的 .catch 能够捕获之前的大谬不然。当然以下代码也能够:

Promise.resolve()

  .then(function success1 (res) {

    throw new Error('error')

  }, function fail1 (e) {

    console.error('fail1: ', e)

  })

  .then(function success2 (res) {

  }, function fail2 (e) {

    console.error('fail2: ', e)

  })

题目十

process.nextTick(() => {

  console.log('nextTick')

})

Promise.resolve()

  .then(() => {

    console.log('then')

  })

setImmediate(() => {

  console.log('setImmediate')

})

console.log('end')

运营结果:

end

nextTick

then

setImmediate

讲明:process.nextTick 和 promise.then 都归属 microtask,而 setImmediate 归属 macrotask,在事变循环的 check 阶段实施。事件循环的种种阶段(macrotask)之间都会实施microtask,事件循环的开首会先进行三遍 microtask。

本文由pc28.am发布于前端技术,转载请注明出处:流程调节,必知必会

上一篇:用功率信号来支配异步流程,web播放控件 下一篇:没有了
猜你喜欢
热门排行
精彩图文