上周5在公司做了关于JS异步编程模型的技术分享,多是内容太干的缘故吧,最后从你们的表情看出“这条粉肠到底在说啥?”的结果:(下面是PPT的讲义,具体的PPT和示例代码在https://github.com/fsjohnhuang/ppt/tree/master/apm_of_js上,有兴趣就上去看看吧!html
《异步编程模型》这个名称确实不太直观,其实今天我想和你们分享的就是上面的代码是如何演进成下面的代码而已。前端
a(function(){ b(function(){ c(function(){ d() }) }) })
TOgit
;(async function(){ await a() await b() await c() await d() }())
咱们知道JavaScript是单线程运行的(撇开Web Worker),而且JavaScript线程执行时浏览器GUI渲染线程没法抢占CPU时间片,所以假如咱们经过如下代码实现60秒后执行某项操做github
const deadline = Date.now() + 60000 while(deadline > Date.now()); console.log('doSomething')
那么浏览器将假死60秒。正常状况下咱们采用异步调用的方式来实现编程
const deadline = Date.now() + 60000 ;(function _(){ if (deadline > Date.now()){ setTimeout(_, 100) } else{ console.log('doSomething') } }())
那到底上述两种方式有什么不一样呢?
到这里我有个疑问,那就是到底什么才叫作异步呢?既然有异步,那必然有同步,那同步又是什么呢?谈起同步和异步,那必不可少地要提起阻塞和非阻塞,那它们又是什么意思呢?promise
谈到它们那必须联系到IO来讲了
阻塞: 就是JS线程发起阻塞IO后,JS线程什么都不作就等则阻塞IO响应。
非阻塞: 就是JS线程发起非阻塞IO后,JS线程能够作其余事,而后经过轮询、信号量等方式通知JS线程获取IO响应结果。
也就是说阻塞和非阻塞描述的是发起IO和获取IO响应之间的时间里,JS线程是否能够继续处理其余任务。
而同步和异步则是描述另外一个方面。
首先当咱们发起网络IO请求时,应用程序会向OS发起系统调用,而后内核会调用驱动程序操做网卡,而后网卡获得的数据会先存放在内核空间中(应用程序是读取不了的),而后将数据从内核空间拷贝到用户空间。抽象一下就是,发起IO请求会涉及到用户空间和内核空间间的数据通讯。浏览器
同步: 应用程序须要显式地将数据从内核空间拷贝到用户空间中,而后再使用数据。
异步: 将数据从内核空间拷贝到用户空间的操做由系统自动处理,而后通知应用程序直接使用数据便可。网络
对于如setTimeout等方法而已,原本就存在用户空间和内核空间的数据通讯问题,所以异步更可能是描述非阻塞这一特性。
那么异步调用的特色就是:
1. 非阻塞
2. 操做结果将于不明确的将来返回dom
举个栗子——番茄炒蛋
番茄切块(代号a)
鸡蛋打成蛋液(代号b)
蛋液煮成半熟(代号c)
将蛋切成块(代号d)
番茄与鸡蛋块一块儿炒熟(代号e)异步
->番茄切块->鸡蛋打成蛋液->蛋液煮成半熟->将蛋切成块->番茄与鸡蛋块一块儿炒熟
a() b() c() d() e()
状况1——全部步骤均无状态依赖
->番茄切块
->鸡蛋打成蛋液
->蛋液煮成半熟
->将蛋切成块
->番茄与鸡蛋块一块儿炒熟
a() b() c() d() e()
状况2——步骤间存在线性的状态依赖
->番茄切块->鸡蛋打成蛋液->蛋液煮成半熟->将蛋切成块->番茄与鸡蛋块一块儿炒熟
a('番茄', function(v番茄块){ b('鸡蛋', function(v蛋液){ c(v蛋液, function(v半熟的鸡蛋){ d(v半熟的鸡蛋, function(v鸡蛋块){ e(v番茄块, v鸡蛋块) }) }) }) })
这就是Callback Hell了
状况3——步骤间存在复杂的状态依赖
异步执行:->番茄切块 |->番茄与鸡蛋块一块儿炒熟
->鸡蛋打成蛋液->蛋液煮成半熟->切成蛋块|
/* 定义任务 */ //番茄切块 const a = (v番茄, cb = id) => doAsyncIO(a, '番茄块', cb) //将鸡蛋打成蛋液 const b = (v鸡蛋, cb = id) => doAsyncIO(b, '蛋液', cb) // 蛋液煮成半熟 const c = (v蛋液, cb = id) => doAsyncIO(c, '半熟的鸡蛋', cb) // 切成蛋块 const d = (v半熟的鸡蛋, cb = id) => doAsyncIO(d, '鸡蛋块', cb) // 番茄与鸡蛋块一块儿炒熟 const e = (v番茄块, v鸡蛋块, cb = id) => doAsyncIO(e, '番茄炒鸡蛋', cb) [a,b,c,d,e].forEach(task =>{ task.state = {done: false} task.done = value => { task.state = {value, done: true} return value } }) /* 执行任务 */ a('番茄') b('鸡蛋', function(v蛋液){ c(v蛋液, function(v半熟的鸡蛋){ d(v半熟的鸡蛋) }) }) poll = () => { if (a.state.done && d.state.done);else return setTimeout(poll, 100) e(a.state.value, d.state.value) } setTimeout(poll, 0)
异步调用所带来的问题是
if...else
,while
等流程控制语句。try...catch
异常机制来处理异常EventProxy做为一个事件系统,经过after、tail等事件订阅方法提供带约束的事件触发机制,“约束”对应“前置条件”,所以咱们能够利用这种带约束的事件触发机制来做为异步执行模式下的流程控制表达方式。
const doAsyncIO = (value, cb) => setTimeout(()=>cb(value), Math.random() * 1000) const ep = new EventProxy() /* 定义任务 */ const a = v番茄 => doAsyncIO('番茄块', ep.emit.bind(ep,'a')) const b = v鸡蛋 => doAsyncIO('蛋液', ep.emit.bind(ep,'b')) const c = v蛋液 => doAsyncIO('半熟的鸡蛋', ep.emit.bind(ep,'c')) const d = v半熟的鸡蛋 => doAsyncIO('鸡蛋块', ep.emit.bind(ep,'d')) const e = (v番茄块, v鸡蛋块) => doAsyncIO('番茄炒鸡蛋', ep.emit.bind(ep,'e')) /* 定义任务间的状态依赖 */ ep.once('b',c) ep.once('c',d) ep.all('a', 'd', e) /* 执行任务 */ a() b()
另外经过error
事件提供对异常机制的支持
ep.on('error', err => { console.log(err) })
但因为EventProxy采用事件机制来作流程控制,而事件机制好处是下降模块的耦合度,但从另外一个角度来讲会使整个系统结构松散难以看出主干模块,所以经过事件机制实现流程控制必然致使代码结构松散和逻辑离散,不过这能够良好的组织形式来让代码结构更紧密一些。
这里的Promise指的是已经被ES6归入囊中的Promises/A+规范及其实现.
Promise至关于咱们去麦当劳点餐后获得的小票,在将来某个时间点拿着小票就能够拿到食物。不一样的是,只要咱们持有Promise实例,不管索取多少次,都能拿到一样的结果。而麦当劳显然只能给你一份食物而已。
代码表现以下
const p1 = new Promise(function(resolve, reject){ /* 工厂函数 * resolve函数表示当前Promise正常结束, 例子: setTimeout(()=>resolve('bingo'), 1000) * reject函数表示当前Promise发生异常, 例子: setTimeout(()=>reject(Error('OMG!')), 1000) */ }) const p2 = p1.then( function fulfilled(val){ return val + 1 } , function rejected(err){ /*处理p1工厂函数中调用reject传递来的值*/ } ) const p3 = p2.then( function fulfilled(val){ return new Promise(function(resolve){setTimeout(()=>resolve(val+1), 10000)}) } , function rejected(err){ /*处理p1或p2调用reject或throw error的值*/ } ) p3.catch(function rejected(err){ /*处理p1或p2或p3调用reject或throw error的值*/ } )
Promises/A+中规定Promise状态为pending(默认值)、fulfilled或rejected,其中状态仅能从pending->fulfilled或pending->rejected,而且可经过then和catch订阅状态变化事件。状态变化事件的回调函数执行结果会影响Promise链中下一个Promise实例的状态。另外在触发Promise状态变化时是能够携带附加信息的,而且该附加信息将沿着Promise链被一直传递下去直到被某个Promise的事件回调函数接收为止。并且Promise还提供Promise.all和Promise.race两个帮助方法来实现与或的逻辑关系,提供Promsie.resolve来将thenable对象转换为Promise对象。
API:
new Promise(function(resolve, reject){})
, 带工厂函数的构造函数
Promise.prototype.then(fulfilled()=>{}, rejected()=>{})
,订阅Promise实例状态从pending到fulfilled,和从pending到rejected的变化
Promise.prototype.catch(rejected()=>{})
,订阅Promise实例状态从pending到rejected的变化
Promise.resolve(val)
, 生成一个状态为fulfilled的Promise实例
Promise.reject(val)
, 生成一个状态为rejected的Promise实例
Promise.all(array)
, 生成一个Promise实例,当array中全部Promise实例状态均为fulfilled时,该Promise实例的状态将从pending转换为fulfilled,若array中某个Promise实例的状态为rejected,则该实例的状态将从pending转换为rejected.
Promise.race(array)
, 生成一个Promise实例,当array中某个Promise实例状态发生转换,那么该Promise实例也随之转
const doAsyncIO = value => resolve => setTimeout(()=>resolve(value), Math.random() * 1000) /* 定义任务 */ const a = v番茄 => new Promise(doAsyncIO('番茄块')) const b = v鸡蛋 => new Promise(doAsyncIO('蛋液')) const c = v蛋液 => new Promise(doAsyncIO('半熟的鸡蛋')) const d = v半熟的鸡蛋 => new Promise(doAsyncIO('鸡蛋块')) const e = ([v番茄块, v鸡蛋块]) => new Promise(doAsyncIO('番茄炒鸡蛋')) /* 执行任务 */ Promise.all([ a('番茄'), b('鸡蛋').then(c).then(d) ]).then(e) .catch(err=>{ console.log(err) })
最大特色:独立的可存储的异步调用结果
其余特色:fulfilled和rejected函数异步执行
jQuery做为前端必备工具,也为咱们提供相似与Promise的工具,那就是jQuery.Deffered
const deffered = $.getJSON('dummy.js') deffered.then(function(val1){ console.log(val1) return !val1 },function (err){ console.log(err) }).then(function(val2){ console.log(val2) })
但jQuery.Deferred并非完整的Promise/A+的实现。
如:
遗留问题!
const a = () => Promise.resolve('a') const b = (v1) => Promise.resolve('b') const c = (v2, v1) => console.log(v1) a().then(b).then(c)
Coroutine中文就是协程,意思就是线程间采用协同合做的方式工做,而不是抢占式的方式工做。因为JS是单线程运行的,因此这里的Coroutine就是一个能够部分执行后退出,后续可在以前退出的地方继续往下执行的函数.
function coroutine(){ yield console.log('c u later!') console.log('welcome guys!') }
其实就是迭代器,跟C#的IEnumrable、IEnumerator和Java的Iterable、Iterator同样。
function* enumerable(){ yield 1 yield 2 } for (let num of enumerable()){ console.log(num) }
如今咱们将1,2替换为代码
function *enumerable(msg){ console.log(msg) var msg1 = yield msg + ' after ' // 断点 console.log(msg1) var msg2 = yield msg1 + ' after' // 断点 console.log(msg2 + ' over') }
编译器会将上述代码转换成
const enumerable = function(msg){ var state = -1 return { next: function(val){ switch(++state){ case 0: console.log(msg + ' after') break case 1: var msg1 = val console.log(msg1 + ' after') break case 2: var msg2 = val console.log(msg2 + ' over') break } } } }
经过调用next函数就能够从以前退出的地方继续执行了。(条件控制、循环、迭代、异常捕获处理等就更复杂了)
其实Generator Function实质上就是定义一个有限状态机,而后经过Generator Function实例的next,throw和return方法触发状态迁移。
next(val)
, 返回{value: val1, done: true|false}
throw(err)
,在上次执行的位置抛出异常
return(val)
,状态机的状态迁移至终止态,并返回{value: val, done: true}
如今咱们用Gererator Function来作番茄炒蛋
const doAsyncIO = value => (resolve) => setTimeout(()=>resolve(value), Math.random() * 1000) /* 定义任务 */ const a = v番茄 => new Promise(doAsyncIO('番茄块')) const b = v鸡蛋 => new Promise(doAsyncIO('蛋液')) const c = v蛋液 => new Promise(doAsyncIO('半熟的鸡蛋')) const d = v半熟的鸡蛋 => new Promise(doAsyncIO('鸡蛋块')) const e = (v番茄块, v鸡蛋块) => new Promise(doAsyncIO('番茄炒鸡蛋')) function* coroutineFunction(){ try{ var p番茄块 = a('番茄') var v蛋液 = yield b('鸡蛋') var v半熟的鸡蛋 = yield c(v蛋液) var v鸡蛋块 = yield d(v半熟的鸡蛋) var v番茄块 = yield p番茄块 var v番茄抄鸡蛋 = yield e(v番茄块, v鸡蛋块) } catch(e){ console.log(e.message) } } const coroutine = coroutineFunction() throwError = coroutine.throw.bind(coroutine) coroutine.next().value.then(function(v蛋液){ coroutine.next(v蛋液).then(function(v半熟的鸡蛋){ coroutine.next(v半熟的鸡蛋).then(function(v鸡蛋块){ coroutine.next().then(function(v番茄块){ coroutine.next(v番茄块).then(function(v番茄抄鸡蛋){ coroutine.next(v番茄抄鸡蛋) }, throwError) }, throwError) }, throwError) }, throwError) })
悲催又回到Callback hell.但咱们能够发现coroutineFunction实际上是以同步代码的风格来定义任务间的执行顺序(状态依赖)而已,执行模块在后面这个让人头痛的Callback hell那里,而且这个Callback Hell是根据coroutineFunction的内容生成,像这种重复有意义的事情天然由机器帮咱们处理最为恰当了,因而咱们引入个状态管理器获得
const doAsyncIO = value => (resolve) => setTimeout(()=>resolve(value), Math.random() * 1000) /* 定义任务 */ const a = v番茄 => new Promise(doAsyncIO('番茄块')) const b = v鸡蛋 => new Promise(doAsyncIO('蛋液')) const c = v蛋液 => new Promise(doAsyncIO('半熟的鸡蛋')) const d = v半熟的鸡蛋 => new Promise(doAsyncIO('鸡蛋块')) const e = (v番茄块, v鸡蛋块) => new Promise(doAsyncIO('番茄炒鸡蛋')) function* coroutineFunction(){ try{ var p番茄块 = a('番茄') var v蛋液 = yield b('鸡蛋') var v半熟的鸡蛋 = yield c(v蛋液) var v鸡蛋块 = yield d(v半熟的鸡蛋) var v番茄块 = yield p番茄块 var v番茄抄鸡蛋 = yield e(v番茄块, v鸡蛋块) } catch(e){ console.log(e.message) } } iPromise(coroutineFunction)
舒爽多了!
ES7引入了async和await两个关键字,Node.js7支持这两货。因而Coroutine写法就更酸爽了.
const doAsyncIO = value => (resolve) => setTimeout(()=>resolve(value), Math.random() * 1000) /* 定义任务 */ const a = v番茄 => new Promise(doAsyncIO('番茄块')) const b = v鸡蛋 => new Promise(doAsyncIO('蛋液')) const c = v蛋液 => new Promise(doAsyncIO('半熟的鸡蛋')) const d = v半熟的鸡蛋 => new Promise(doAsyncIO('鸡蛋块')) const e = (v番茄块, v鸡蛋块) => new Promise(doAsyncIO('番茄炒鸡蛋')) async function coroutine(){ try{ var p番茄块 = a('番茄') var v蛋液 = await b('鸡蛋') var v半熟的鸡蛋 = await c(v蛋液) var v鸡蛋块 = await d(v半熟的鸡蛋) var v番茄块 = await p番茄块 var v番茄抄鸡蛋 = await e(v番茄块, v鸡蛋块) } catch(e){ console.log(e.message) } } coroutine()
到这里各位应该会想“不就作个西红柿炒鸡蛋吗,搞这么多,至于吗?”。其实个人见解是
我曾梦想有一天全部浏览器都支持Promise,async和await,你们能够不明就里地写出coroutine,完美地处理异步调用的各类问题。直到有一天知道世上又多了Rxjs这货,不说了继续填坑去:)
尊重原创,转载请注明来自:http://www.cnblogs.com/fsjohnhuang/p/6109701.html ^_^肥仔John