流程序调整制,必知必会

By admin in 4858美高梅 on 2019年5月3日

初探 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);

绝大诸多编制程序语言同步试行每行代码。第2行推行完成再次来到一个结出。无论第三行代码推行多长期,只有实行到位第2行代码才会施行。

Promise 异步流程序调整制

2017/10/04 · JavaScript
· Promise

初稿出处: 麦子谷   

姚丽冰    学号:16050120089

什么是 Headless Chrome

Headless Chrome 是 Chrome
浏览器的无分界面形态,能够在不展开浏览器的前提下,使用具备 Chrome
辅助的特点运转你的顺序。比较于今世浏览器,Headless Chrome 尤其有利于测试
web 应用,得到网址的截图,做爬虫抓取新闻等。相比于出道较早的
PhantomJS,SlimerJS 等,Headless Chrome 则更为接近浏览器情形。

单线程管理程序

JavaScript
是单线程的。当浏览器选项卡实践脚本时,别的全部操作都会终止。那是早晚的,因为对页面
DOM 的更改不可能并发实践;三个线程
重定向 ULacrosseL 的同时,另几个线程正要增添子节点,这么做是朝不保夕的。

用户不轻巧开掘,因为管理程序会以组块的款型火速试行。比方,JavaScript
检测到按键点击,运维总计,并立异DOM。一旦成功,浏览器就能够自便处理队列中的下八个档案的次序。

(附注: 其余语言举例 PHP 也是单线程,不过经过拾二线程的服务器比如 Apache
管理。同壹 PHP
页面同时提倡的五个请求,可以运维四个线程运营,它们是相互隔开分离的 PHP
实例。
)

4858美高梅 1前言

多年来部门在招前端,作为机构唯1的前端,面试了广大应聘的同校,面试中有1个事关
Promise 的1个标题是:

网页中预加载20张图片财富,分步加载,三遍加载10张,一次到位,怎么调控图片请求的产出,怎么样感知当前异步请求是不是已成功?

然而能壹切答上的很少,能够交给1个回调 +
计数版本的,笔者都感到合格了。那么接下去就协同来学学总计一下依照 Promise
来拍卖异步的二种方法。

本文的事例是3个极其简化的三个卡通阅读器,用四张漫画图的加载来介绍异步管理不相同格局的得以完结和差距,以下是
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>

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

怎么获得 Headless Chrome

目前,Mac 上 Chrome 59
beta流程序调整制,必知必会。 版本与
Linux 上的 Chrome 伍7+ 已经先河帮衬 headless 特性。Windows 上 Chrome
一时半刻不支持,能够动用 Chrome Canary
60 实行开垦。

透过回调达成异步

单线程发生了二个标题。当 JavaScript
实行3个“缓慢”的管理程序,比方浏览器中的 Ajax
请求大概服务器上的数据库操作时,会生出怎样?那几个操作可能须求几分钟 –
照旧几分钟。浏览器在等待响应时会被锁定。在服务器上,Node.js
应用将不能管理其余的用户请求。

缓慢解决方案是异步管理。当结果就绪时,3个进程被告知调用另一个函数,而不是等待实现。那称为回调,它作为参数传递给其余异步函数。比如:

doSomethingAsync(callback1); console.log(‘finished’); // 当
doSomethingAsync 落成时调用 function callback一(error) { 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()
接收回调函数作为参数(只传递该函数的引用,由此支付非常的小)。doSomethingAsync()
实行多久并不根本;大家所理解的是,callback1()
将要今后某些时刻试行。调控台将显得:

finished doSomethingAsync complete

1
2
finished
doSomethingAsync complete

单一请求

最简易的,正是将异步3个个来管理,转为一个类似同步的法子来拍卖。
先来轻松的得以落成一个单个 Image 来加载的 thenable
函数和3个管理函数再次来到结果的函数。

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
  })
}

异步转同步的缓和理念是:当第3个 loadImg(urls[1]) 实现后再调用
loadImg(urls[2]),依次往下。倘使 loadImg()
是两个联合签名函数,那么很当然的想到用__循环__。

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[4858美高梅,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 变量就好像3个迭代器,不断指向最新的回到的
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(‘第三个参数必须是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)

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。

本来那种异步转同步的办法在那三个例证中并不是最佳的解法,但当有适度的事情场景的时候,那是很宽泛的消除方案。

【嵌牛导读】Promise 想必大家都10分纯熟,想想就那么多少个api,然而您真的通晓 Promise 吗?

怎么在巅峰中利用

在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,你会看出如下的分界面:4858美高梅 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
并等待动画实现,或者要求嵌套两到三层,然而普通还算可管制。

操作系统或服务器进程的情景就分裂了。3个 Node.js API
能够吸收接纳文件上传,更新两个数据库表,写入日志,并在发送响应在此以前开始展览下一步的
API 调用。

出现请求

说起底同一域名下能够并发多少个 HTTP
请求,对于那种无需按梯次加载,只供给按梯次来拍卖的面世请求,Promise.all
是最棒的搞定办法。因为Promise.all 是原生函数,咱们就引述文书档案来解释一下。

Promise.all(iterable) 方法指当全部在可迭代参数中的 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。’) })

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

【嵌牛鼻子】:Promise

远程序调节制

在上文中讲述的都使用终端命令运行 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 和 Cookie 等音讯。

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
对象会执行以下七个函数(作为参数字传送递的)当中之1:

  • 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); }); }); }

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 八.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) { setTimeout(() => { callback(null, ‘done’); }, time); }
const asyncWait = promisify(wait); ayscWait(一千);

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.all 就算能并发四个请求,然而壹旦中间某多个 promise 出错,整个
promise 会被 reject 。 webapp 里常用的财富预加载,只怕加载的是 20
张逐帧图片,当网络现身难题, 20
张图难免会有1两张呼吁退步,借使战败后,直接丢掉别的被 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
并发请求,顺序处理结果

【嵌牛提问】:本文依照 Promise
的局地知识点总结了10道题,看看你能做对几道。

在 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

结果4858美高梅 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()) }) .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
支持,可是 TC3玖 技委已经宣告了 .finally()
补丁。

function doSomething() { doSomething1() .then(doSomething二)
.then(doSomething三) .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(() => {
    // 清理操作放这儿!
  });
}

支配最大并发数

将来大家来试着落成一下方面包车型地铁笔试题,那个其实都__无需调整最大并发数__。
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 + 一}
张图片加载完毕.`) }) }, Promise.resolve()) .then(() => {
console.log(‘>> 后边10张已经加载完!’) }) .then(() => {
while(index < 20) {
step二.push(loadImg(`./images/pic/${index}.jpg`)) index += 1 } return
step2.reduce((task, imgPromise, i) => { return task .then(() =>
imgPromise) .then(() => { console.log(`第 ${i + 1①}
张图片加载实现.`) }) }, Promise.resolve()) }) .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…

demo四地址(看调整台和互连网请求):Promise 分步加载 –

那便是说大家在空虚一下代码,写一个通用的点子出来,这么些函数重返3个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张图纸,当一张图片加载成功后,又会持续发起一张图纸的呼吁,让并发数保持在
十个,直到必要加载的图样都全体倡导呼吁。这几个在写爬虫中能够说是比较常见的采纳意况了。
那么大家依照上边的片段学问,大家用两种办法来促成这几个成效。

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

小结

本文简单介绍了一下 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 收藏
评论

4858美高梅 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() 会立刻甘休。

使用递归

若是大家的最大并发数是 四 ,那种方法的机要思虑是一定于 五个__纯净请求__的 Promise 异步职务在同时运营运转,5个纯粹请求不断递归取图片 URL 数组中的 U锐界L 发起呼吁,直到 UENVISIONL
全部取完,最终再使用 Promise.all
来拍卖最终还在呼吁中的异步任务,大家复用第2节__递归__本子的思绪来落实那一个作用:

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)
}

设定最大请求数为 五,Chrome 中呼吁加载的 timeline
4858美高梅 5

demo6地址(看调节台和网络请求):Promise 调控最大并发数 –
方法1

题目一

利用 Promise.race() 管理七个异步操作

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);
  });

使用 Promise.race

Promise.race 接受一个 Promise 数组,再次来到那些数组中伊始被 resolve 的
Promise 的再次来到值。终于找到 Promise.race
的运用意况了,先来利用那么些措施实现的效能代码:

JavaScript

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 大于1切图形数, 并发全体请求 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, 一)
promises.push(wrapHandler(url)) }) }, Promise.resolve()).then(() => {
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
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
4858美高梅 6

demo7地址(看调控台和互联网请求):Promise 调整最大并发数 –
方法二

在选拔 Promise.race 完结这么些成效,重纵然连绵不断的调用 Promise.race
来回到已经被 resolve 的职责,然后从 promises 中删掉这么些 Promise
对象,再进入二个新的 Promise,直到整个的 UPRADOL 被取完,最终再使用
Promise.all 来处理全部图片落成后的回调。

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

以后光明呢?

Promise 减少了回调地狱,可是引进了别的的难题。

学科平常不提,整个 Promise 链条是异步的,1雨后春笋的 Promise
函数都得回来自身的 Promise 只怕在结尾的 .then().catch() 或者
.finally() 方法里面实施回调。

自个儿也明确:Promise
干扰了本人很久。语法看起来比回调要复杂,多数地方会出错,调节和测试也成难点。可是,学习基础依旧很要紧滴。

延长阅读:

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

写在最终

因为工作内部大批量施用 ES陆 的语法,Koa 中的 await/async 又是 Promise
的语法糖,所以领会 Promise
各个流程序调整制是对作者来讲是相当重要的。写的有不清楚的地方和有荒唐的位置迎接我们留言指正,其它还有任何没有关联到的方法也请大家提供一下新的法子和格局。

  console.log(1)

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)) }); } // 运转 connect
方法 (自实践情势) (() => { connect(); .then(result =>
console.log(result)) .catch(err => console.log(err)) })();

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
    证明,能够保险下2个命令试行前,它已实行到位

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 () => { 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 )和 Node七.陆+ 均已得以达成。

如是说,未有全面包车型地铁…

题外话

我们脚下有 一 个前端的 HC,base 卡拉奇,一家具备 50
架飞机的物流集团的AI部门,须要专门的学业经验三年以上,那是商号社招供给的。
感兴趣的就联系本身呢,Email: d2hlYX瑞鹰vQGZveG一haWwuY2九t

  resolve()

Promises, Promises

async / await 依旧借助 Promise 对象,最后看重回调。你须求知道
Promise 的办事规律,它也并分化 Promise.all()
Promise.race()。相比较易于忽视的是
Promise.all(),这几个命令比选择一多种非亲非故的 await 命令更飞快。

参考资料

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

    1 赞 3 收藏
    评论

4858美高梅 7

  console.log(2)

一路循环中的异步等待

某个情状下,你想要在一齐循环中调用异步函数。比如:

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);
  });
}

巡回本中国人民保险公司持同步,并且总是在中间异步操作在此以前完毕。

ES201八 引进异步迭代器,除了 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), log =
await asyncLogAccess(user); return true; } // 使用高阶函数捕获错误
function catchErrors(fn) { return function (…args) { return
fn(…args).catch(err => { console.log(‘EHighlanderROLX570’, err); }); } } (async
() => { await catchErrors(connect)(); })();

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
    的易用性
  • TC3玖 异步函数标准
  • 用异步函数简化异步编码

promise.then(() => {

JavaScript 之旅

异步编制程序是 JavaScript
不能制止的挑衅。回调在好多选用中是不能缺少的,不过轻巧陷入深度嵌套的函数中。

Promise
抽象了回调,可是有为数不少句法陷阱。调换已有函数恐怕是1件苦差事,·then()
链式调用看起来很混乱。

很幸运,async/await
表达清晰。代码看起来是一路的,不过又不独占单个管理线程。它将改成您书写
JavaScript 的秘诀,以至让你更讲究 Promise – 要是没接触过的话。

1 赞 收藏
评论

4858美高梅 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 有 三 种状态:pending、fulfilled 或
rejected。状态退换只好是 pending->fulfilled 可能pending->rejected,状态一旦改造则无法再变。上面 promise贰 并不是
promise一,而是重临的二个新的 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
唯有首先次试行有效,多次调用未有别的意义,呼应代码2结论: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
内部景色1经济体退换,并且有了一个值,那么继续每回调用 .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!!!’)

因为重返任性1个非 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
的第1个管理错误的函数捕获不了第3个管理成功的函数抛出的一无所能,而后续的
.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,事件循环的起先会先实行2次 microtask。

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图
Copyright @ 2010-2019 美高梅手机版4858 版权所有