第一章 异步:如今与未来web
程序中如今运行的部分和未来运行的部分之间的关系就是异步编程的核心。ajax
场景:等待用户输入、从数据库或文件系统中请求数据、经过网络 发送数据并等待响应,或者是在以固定时间间隔执行重复任务(好比动画)算法
1.1 分块的程序数据库
最多见的块单位是函数。 从如今到未来的等待,最简单的方法是使用一个一般称为回调函数的函数。编程
只要把一段代码包装成一个函数,并指定它在响应某个事件(定时器、鼠标点 击、Ajax 响应等)时执行,你就是在代码中建立了一个未来执行的块,也由此在这个程序 中引入了异步机制。数组
1.2 事件循环promise
一个事件循环处理的示例浏览器
var eventLoop = []; var event; var reportError = function(err) { console.log('An error happened!'); console.log(err); } // 永远循环 while(true){ // 一次tick if(eventLoop.length) { // 移出第一个事件 event = eventLoop.shift(); try{ event(); } catch(err){ reportError(err); } } }
诸如setTimeout()等方法,并不会直接将回调函数挂在事件循环队列中,而是设置一个定时器,等定时器到时后,环境会把回调函数放在事件循环中。性能优化
1.3 并行线程websocket
并行是关于可以同时发生的事情。 并行最多见的就是进程和线程。进程和线程独立运行,并可能同时运行:多个线程可以共享单个进程的内存。 JS因为其单线程的特性,其不肯定性是在函数(事件)顺序级别上,而不是多线程状况下的语句顺序级别。这种函数顺序的不肯定性就是常说的竞态条件(race condition)。
1.4 并发
1.4.1 非交互
若是进程间没有互相影响的话,不肯定性是彻底能够接受的。
1.4.2 交互
并发的操做须要相互交流,经过做用域或DOM间接交互。若是出现这样的交互,就须要对它们的交互进行协调以免竞态的出现。 一个可能的优化是
// 假设分页状况下 var res = []; function response(data) { res[data.page] = data.items; } ajax('url?page=0',response); ajax('url?page=1',response);
1.4.3 协做
并发协做:取到一个长期运行的进程,并将其分割成多个步骤或多批任务,使得其余并发“进程”有机会将本身的运算插入到时间循环队列中交替运行。 本质上是对于一个高性能消耗的操做,将其分割开进行处理。
var res = []; function response(data) { // 假设1000万条数据,一次处理1000条 var chunk = data.splice(0, 1000); res = res.concat(chuck.map((value)=>{ return value * 2; })); // 若是有后续,异步触发 if(data.length > 0) { setTimeout(function() { response(data); }, 0); } }
1.5 任务
任务队列(job queue)。它是挂在事件循环队列的每一个 tick 以后 的一个队列。在事件循环的每一个 tick 中,可能出现的异步动做不会致使一个完整的新事件 添加到事件循环队列中,而会在当前 tick 的任务队列末尾添加一个项目(一个任务)。
第二章 回调
2.2 顺序的大脑
2.2.1 执行与计划
异步不是多任务,而是快速的上下文切换。 编写异步代码的困难之处就在于,这种思考/计划的意识流对咱们中的绝大多数来讲是不天然的。
2.3 信任问题
类型检查/规范化的过程对于函数输入是很常见的,即便是对于理论上彻底能够信任的代码。
function addNumbers(x, y) { // 为了防止变成字符串拼接,检查类型,若是不符合就抛出错误 if(typeof x !== 'number' || typeof y !== 'number') { throw new Error('Bad arguments!'); } // 另外一种作法是提早转换类型 x = Number(x); y = Number(y); return x + y; }
永远异步调用回调,即便就 在事件循环的下一轮,这样,全部回调就都是可预测的异步调用了。
第三章 Promise
回调表达程序异步和管理并发的两个主要缺陷:缺少顺序性 和可信任性
3.1什么是Promise
var PENDING = 0; var FULFILLED = 1; var REJECTED = 2; function Promise(fn) { this._state = PENDING; this._value = null; // 用树形结构去记录then注册的hanlder,而后等到主Promise决议了,在_excuteThen中操做then返回Promise的状态和结果值 this._handlers = []; if (fn) { this._enqueue(fn.bind(null, this._resolve.bind(this), this._reject.bind(this))); } return this; } /** * Promise的then方法。 * 注册新的handler,挂载到handlers树上。 * 返回该hanlder的promise引用,用于链式调用。 * 返回的promise的_value取决于then传入的onFulfilled或onRejected函数的返回值。 * @param {*} onFulfilled * @param {*} onRejected */ Promise.prototype.then = function (onFulfilled, onRejected) { var handler = { onFulfilled: onFulfilled, onRejected: onRejected, promise: new Promise(), }; // 先压入堆栈,造成树形结构 var index = this._handlers.push(handler) - 1; // 已经决议了,直接执行then if (this._state !== PENDING) { this._excuteThen(handler); } return this._handlers[index].promise; }; /** * Promise的resolve方法。 * 行为包括状态置位,修改结果,而后执行先前压入堆栈的then * @param {*} result */ Promise.prototype._resolve = function (result) { // 只有PENDING状况下才能修改状态 if (this._state === PENDING) { this._state = FULFILLED; this._value = result; this._handlers.forEach(this._excuteThen.bind(this)); } }; /** * Promise的reject方法 * @param {*} result */ Promise.prototype._reject = function (result) { // 只有PENDING状况下才能修改状态 if (this._state === PENDING) { this._state = REJECTED; this._value = reason; this._handlers.forEach(this._excuteThen.bind(this)); } }; /** * 执行then,同时也通知then返回的promise去执行resolve或reject。 * then中的_value最终使用的值是then中onFulfilled或onRejected的返回值。 * @param {*} handler */ Promise.prototype._excuteThen = function (handler) { if (this._state === FULFILLED && typeof handler.onFulfilled === 'function') { // 异步触发then this._enqueue( handler.promise._resolve.bind( handler.promise, handler.onFulfilled(this._value) ) ); // 同步触发then /* handler.promise._reject(handler.onFulfilled(this._value)); */ } if (this._state === REJECTED && typeof handler.onRejected === 'function') { this._enqueue( handler.promise._resolve.bind( handler.promise, handler.onRejected(this._value) ) ); /* handler.promise._reject(handler.onRejected(this._value)); */ } }; /** * 将fn加入事件循环 * @param {Function} fn */ Promise.prototype._enqueue = function(fn){ // process.nextTick(fn); // NodeJS setTimeout(fn.bind(this), 0); };
3.2 设置超时:
// 用于超时一个Promise的工具 function timeoutPromise(delay) { return new Promise( function(resolve,reject){ setTimeout( function(){ reject( "Timeout!" ); }, delay ); } ); } // 设置foo()超时 Promise.race( [ foo(), // 试着开始foo() timeoutPromise( 3000 ) // 给它3秒钟 ] ) .then( function(){ // foo(..)及时完成! }, function(err){ // 或者foo()被拒绝,或者只是没能按时完成 // 查看err来了解是哪一种状况 } );
3.3 promise一旦决议就不会改变:
then()注册的回调中,出现了JavaScript异常错误怎么办?并不会执行then中的异常回调函数!!当捕获到错误,会执行下一个then的reject回调函数,或被catch捕获。
var p = new Promise( function(resolve,reject){ resolve( 42 ); } ); p.then( function fulfilled(msg){ foo.bar(); console.log( msg ); // 永远不会到达这里 :( }, function rejected(err){ // 永远也不会到达这里 } ); // p 已经完成 为值 42,因此以后查看 p 的决议时,并不能由于出错就把 p 再变为一个拒绝。
3.4 promise链式流:
能够把多个 Promise 链接到一块儿以表示一系列异步 步骤,每一个 Promise 的决 议就成了继续下一个步骤的信号。
两个 Promise 固有行为特性:
Promise. resolve(..) 会直接返回接收到的真正 Promise,或展开接收到的 thenable 值,并在持续展 开 thenable 的同时递归地前进。若是咱们向封装的 promise 引入异步,一 切都仍然会一样工做
var p = Promise.resolve( 21 );
p.then( function(v){
console.log( v ); // 21
// 建立一个promise并返回
return new Promise( function(resolve,reject){
// 引入异步!
setTimeout( function(){
// 用值42填充
resolve( v * 2 );
}, 100 );
} );
} ).then( function(v){
// 在前一步中的100ms延迟以后运行
console.log( v ); // 42
} );
构造ajax请求:
// 假定工具ajax({url}, {callback})存在 // Promise-aware ajax function request(url) { return new Promise(function(resolve, reject) { // ajax(...) 回调应该是咱们这个promise的resolve函数 ajax(url, resolve); }); } request('http://some.url.1/').then(function(response1) { return request('http://some.url.2/?v=' + response1); }).then( function(response2){ console.log(response2); })
3.5 错误处理
错误处理最天然的形式就是同步的 try..catch 结构,遗憾的是,它只 能是同步的,没法用于异步代码模式。
为了不丢失被忽略和抛弃的 Promise 错误,一些开发者表示,Promise 链的一个最佳实践 就是最后总以一个 catch(..) 结束
3.6 Promise模式
3.6.1 Promise.all([...])
完成顺序并不重要,可是必须都要完成,门才能打开并让流程控制 继续。
Promise.all([...])容许传入一组Promise对象,调用返回的promise会收到一个完成消息,这是一个由全部传入promise的完成消息组成的数组,与指定的顺序一致。 它会在全部成员的promise都完成(fulfilled)后才会完成(fulfilled)。当其中任意一个被拒绝(rejected),它就马上进入被拒绝(rejected)状态,并丢弃来自其余全部promise的所有结果。
3.6.2 Promise.race([...])
竞态:只响应“第一个跨过终点线的 Promise”,而抛弃其余 Promise。
第一个返回的promise为完成,它就会进入完成(fulfilled)状态;第一个返回的promise为失败,它就会进入被拒绝(rejected)状态。
应用:1. 超时模式
// 前面定义的timeoutPromise(..)返回一个promise, // 这个promise会在指定延时以后拒绝 // 为foo()设定超时 Promise.race( [ foo(), // 启动foo() timeoutPromise( 3000 ) // 给它3秒钟 ] ) .then( function(){ // foo(..)按时完成! }, function(err){ // 要么foo()被拒绝,要么只是没可以按时完成, // 所以要查看err了解具体缘由 } );
finally:前面例子中的 foo() 保留了一些要用的资源,可是出现了超时,致使这个 promise 被忽略,finally就是用来:超时后主动释放这些保留资源,或者取消任何可能产生的反作用。
Promise 须要一个 finally(..) 回调注册,这个回调在 Promise 决议后总 是会被调用,而且容许你执行任何须要的清理工做
var p = Promise.resolve( 42 );
p.then( something ) .finally( cleanup ) .then( another ) .finally( cleanup );
3.6.3 其余变体
另外一方面,Promise也有原型方法finally,在决议后忽略是否成功,老是会执行。
3 promise API 综述
3.7.1 new Promise(..) 构造器
构造器 Promise(..) 必须和 new 一块儿使用,而且必须提供一个函数回调,函数接受两个函数回调,用以支持 promise 的决议。这两个函数称为 resolve(..) 和 reject(..):
3.7.2 Promise.resolve(..) 和 Promise.reject(..)
建立成功和拒绝的Promise的快捷方式
3.7.3 then(..) 和 catch(..)
Promise 决议以后,当即会调用 这两个处理函数之一,但不会两个都调用,并且老是异步调用
3.8 Promise局限性
3.8.1 顺序错误处理
若是构建了一个没有错误处理函数的Promise链,链中任何地方的任何错误都会在链中一直传播下去。
3.8.2 单一值
Promise只能有一个完成值,或一个拒绝理由。能够进行必定的值封装,可是须要每一步都进行封装和解封。
3.8.3 单决议
Promise只能被决议一次。
3.8.4 惯性
基于Promise对原有的异步回调进行改造依赖于经验,没有通用的实现手段。
3.8.5 没法取消的Promise
单独的Promise不该该可取消,可是取消一个序列(集合在一块儿的Promise构成的链)是合理的。
3.8.6 性能
相比于不受信任的裸回调,Promise性能上更慢一点。
第四章 async await 异步处理
4.1 async-await 和 Promise 的关系
async-await是promise和generator的语法糖。简单来讲:async-await 是创建在 promise机制之上的。
基本语法
async function basicDemo() { let result = await Math.random(); console.log(result); } basicDemo(); // 0.6484863241051226 //Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: undefined}
async
async用来表示函数是异步的,定义的函数会返回一个promise对象,可使用then方法添加回调函数。
async function demo01() { return 123; } demo01().then(val => { console.log(val);// 123 }); 若 async 定义的函数有返回值,return 123;至关于Promise.resolve(123),没有声明式的 return则至关于执行了Promise.resolve();
await
await 能够理解为是 async wait 的简写。await 必须出如今 async 函数内部,不能单独使用。
function notAsyncFunc() { await Math.random(); } notAsyncFunc();//Uncaught SyntaxError: Unexpected identifier
await 后面能够跟任何的JS 表达式。虽说 await 能够等不少类型的东西,可是它最主要的意图是用来等待 Promise 对象的状态被 resolved。若是await的是 promise对象,会形成异步函数中止执行,而且等待 promise 的解决,若是等的是正常的表达式则当即执行。
function sleep(second) { return new Promise((resolve, reject) => { setTimeout(() => { resolve(' enough sleep~'); }, second); }) } function normalFunc() { console.log('normalFunc'); } async function awaitDemo() { await normalFunc(); console.log('something, ~~'); let result = await sleep(2000); console.log(result);// 两秒以后会被打印出来 } awaitDemo(); // normalFunc // VM4036:13 something, ~~ // VM4036:15 enough sleep~
实例
举例说明啊,你有三个请求须要发生,第三个请求是依赖于第二个请求的解构第二个请求依赖于第一个请求的结果。若用 ES5实现会有3层的回调,若用Promise 实现至少须要3个then。一个是代码横向发展,另外一个是纵向发展。今天指给出 async-await 的实现哈~
//咱们仍然使用 setTimeout 来模拟异步请求 function sleep(second, param) { return new Promise((resolve, reject) => { setTimeout(() => { resolve(param); }, second); }) } async function test() { let result1 = await sleep(2000, 'req01'); let result2 = await sleep(1000, 'req02' + result1); let result3 = await sleep(500, 'req03' + result2); console.log(` ${result3} ${result2} ${result1} `); } test(); //req03req02req01 //req02req01 //req01
错误处理
上述的代码好像给的都是resolve的状况,那么reject的时候咱们该如何处理呢?
最好把 await 命令放在 try...catch 代码块中。
function sleep(second) { return new Promise((resolve, reject) => { setTimeout(() => { reject('want to sleep~'); }, second); }) } async function errorDemo() { let result = await sleep(1000); console.log(result); } errorDemo();// VM706:11 Uncaught (in promise) want to sleep~ // 为了处理Promise.reject 的状况咱们应该将代码块用 try catch 包裹一下 async function errorDemoSuper() { try { let result = await sleep(1000); console.log(result); } catch (err) { console.log(err); } } errorDemoSuper();// want to sleep~ // 有了 try catch 以后咱们就可以拿到 Promise.reject 回来的数据了。 // 另外一种写法 async function myFunction() { await somethingThatReturnsAPromise().catch(function (err){ console.log(err); }); }
当心你的并行处理!!!
我这里为啥加了三个感叹号呢~,由于对于初学者来讲一不当心就将 ajax 的并发请求发成了阻塞式同步的操做了,我就真真切切的在工做中写了这样的代码。await 若等待的是 promise 就会中止下来。业务是这样的,我有三个异步请求须要发送,相互没有关联,只是须要当请求都结束后将界面的 loading 清除掉便可。
刚学完 async await 开心啊,处处乱用~
function sleep(second) { return new Promise((resolve, reject) => { setTimeout(() => { resolve('request done! ' + Math.random()); }, second); }) } async function bugDemo() { await sleep(1000); await sleep(1000); await sleep(1000); console.log('clear the loading~'); } bugDemo();
loading 确实是等待请求都结束完才清除的。可是你认真的观察下浏览器的 timeline 请求是一个结束后再发另外一个的(若观察效果请发真实的 ajax 请求)
那么,正常的处理是怎样的呢?
async function correctDemo() { let p1 = sleep(1000); let p2 = sleep(1000); let p3 = sleep(1000); await Promise.all([p1, p2, p3]); console.log('clear the loading~'); } correctDemo();// clear the loading~
恩, 完美。看吧~ async-await并不能取代promise.
await in for 循环
最后一点了,await必须在async函数的上下文中的。
// 正常 for 循环 async function forDemo() { let arr = [1, 2, 3, 4, 5]; for (let i = 0; i < arr.length; i ++) { await arr[i]; } } forDemo();//正常输出 // 由于想要炫技把 for循环写成下面这样 async function forBugDemo() { let arr = [1, 2, 3, 4, 5]; arr.forEach(item => { await item; }); } forBugDemo();// Uncaught SyntaxError: Unexpected identifier
第五章 程序性能
5.1 Web Worker
JavaScript目前并无支持多线程执行的功能。 目前部分浏览器支持经过Web Worker的形式来实现任务并行。一般Web Worker只加载JS文件,浏览器为它启动一个独立的线程,让这个文件在这个线程中为独立的程序运行。
var w1 = new Worker('http://some.url.com/webworker.js');
Worker之间以及它们和主程序之间,不会共享任何做用域或资源,而是经过一个基本的事件消息机制相互联系。好比
w1.addEventListener('message', function(evt) { // evt.data }); w1.postMessage('something to say');
专用Worker和建立它的主程序是一对一的关系,这个message要么来自这个Worker,要么来自主页面。 Worker能够实例化它的子Worker,称为subworker。 要想关闭一个worker,只要在主程序中对Worker调用terminate()方法(相似浏览器标签页来关闭页面)。
5.1.1 Worker环境
在Worker内部是没法访问主程序的任何资源的(变量、DOM)。 它能够用来执行网络操做(ajax,websocket)以及设置定时器,并能够访问几个重要的全局变量和功能的本地复本,好比navigator、location、JSON和applicationCache。 还能够经过importScript(...)方法向Worker加载额外的JavaScript脚本:
// Worker内部,该操做是同步的 importScript('foo.js', 'bar.js');
Web Worker的应用:
5.1.2 数据传递
若是要传递一个对象,可使用结构化克隆算法,把这个对象复制到一边。 或者可使用Transferable对象,发生对象全部权的转移,数据自己不作移动。一旦全部权发生转移,它原来的位置上就会变为null或不可访问,消除多线程编程做用域共享带来的混乱。好比使用postMessage()方法发送一个Transferable对象:
// 假设foo是一个Uint8Array postMessage(foo.buffer, [foo.buffer]);
5.1.3 共享Worker
使用SharedWorker能够建立一整个站点或app的全部页面实例均可以共享的中心Worker。
var w1 = new SharedWorker('http://some.url.com/sharedWorker.js');
SharedWorker使用端口来做为不一样程序的惟一标识符,调用程序必须使用Worker的port对象用于通讯。
w1.port.addEventListener('message', handleMessages); w1.port.postMessage('something'); // 端口链接初始化 w1.port.start();
同时在SharedWorker内部,还须要处理额外的connect事件,为这个特定的链接提供了端口对象。保持多个链接独立的最简单办法就是使用port上的闭包,把这个链接上的事件侦听和传递定义在connect的处理函数内部:
// 在SharedWorker内部 addEventListener('connect', function(evt) { // 这个链接分配的端口 var prot = evt.ports[0]; port.addEventListener('message', function(evt) { // ... port.postMessage('something'); // ... }); port.start(); });
5.2 SIMD(单指令多数据)
单指令多数据是一种数据并行(data parallelism)的方式,这不是把程序逻辑分红并行的块,而是并行处理数据的多个位。现代CPU经过数字“向量”(特定类型的数组),以及能够在全部这些数字上并行操做的指令,来提供SIMD功能。这是利用低级指令级并行的底层运算。
5.3 asm.js
5.3.1 如何使用asm.js优化
var a = 42; var b = a; // asm优化,确保b是32位整型 var b = a | 0;
5.3.2 asm.js模块
对JS性能影响最大的因素是内存分配、垃圾收集和做用域访问。可使用asm.js模块来解决这些问题。 对于一个asm.js模块来讲,须要明确地导入一个严格规范的命名空间——stdlib,以导入必要的符号。同时还须要声明一个堆(heap)并将其传入var heap = new ArrayBuffer(0x10000); //64k堆,asm.js就能够在这个缓冲区存储和获取值,不须要付出任何内存分配和垃圾收集的代价。 asm的目标是针对特定的任务处理提供一种优化的方法,好比数学运算和游戏中的图像处理。
第六章 性能测试与调优
6.1 性能测试
使用Benchmark.js、mocha、karma等测试框架。
6.5 微性能
若是某个变量只在一个位置被引用,而别处没有任何引用,那么它的值就会被在线化,即直接用值替换变量。 当递归能够进行展开,引擎就会对其进行采用循环的方式实现。 非关键路径上的优化没有必要,而应该注重可读性。关键路径要注重性能优化。
6.6 尾调用优化
使用TCO能够将尾递归转为普通的循环
function tco(f) { var value, active = false, accumulated = []; // 在f内部进行递归调用的时候,其实调用的是这个返回的accumulator // 这里须要注意的是,要将f中的递归调用函数名使用accumulator被赋予的变量名 return function accumulator() { accumulated.push(arguments); if(!active) { active = true; /** * 因为每次f.apply都会致使accumulated被push进一个新的arguments, * 因此这个while一直要到所有执行结束才会跳出, * 但这种结构只会保持最多两层的调用栈 */ while(accumulated.length) { value = f.apply(this, accumulated.shift()); } active = false; return value; } } } var sum = tco(function(x, y) { if (y > 0) { // 再次调用的实际上是闭包返回的accumulator return sum(x + 1, y - 1) } else { return x } }); st=>start: 开始 ed=>end: 结束 cond1=>condition: 检查accumulator.length (若是是第一次进入, 或者上一次的递归调用中作了push, 这个循环就会继续) cond2=>condition: 须要进行递归 opm0=>subroutine: 初始化闭包内的变量, 调用闭包返回的函数, 执行accumulated.push(arguments), 第一次的active判断必然经过 op1-1=>operation: while循环 op1-2=>operation: 取出上次的运行结果, 传参给f.apply op2-1=>operation: 执行真正的操做 op2-2=>operation: 进入第二层递归调用 op2-3=>operation: 执行 accumulated.push(arguments), 记录这一次递归调用的结果 op2-4=>operation: 第二层中!active为false,返回 op2-5=>operation: 回到第一层的while循环 op3-1=>operation: 递归调用条件已经不符合,能够返回结果,再也不作push op4-1=>operation: 已经执行完毕,value获得最后的结果 st->opm0->op1-1->cond1 cond1(yes,right)->cond2 cond1(no)->op4-1->ed cond2(yes,right)->op1-2->op2-1->op2-2->op2-3->op2-4->op2-5(left)->op1-1 cond2(no,right)->op3-1(right)->op1-1