上一篇文章介绍了js异步的底层基础--Event Loop模型,本文将介绍JS中传统的几种异步操做实现的模式。jquery
回调函数是异步的最基本实现方式。es6
// 例子:回调函数 const f1 = (callback) => setTimeout(()=>{ console.log('f1') // 自身要执行的函数内容 callback() },1000) const f2 = () =>{ console.log('f2') } f1(f2)
缺点:ajax
fs.readFile
等函数,只提供传入一个回调函数,若是想触发2个回调函数,就只能再用一个函数把这两个函数包起来// 例子1:回调地狱,依次执行f1,f2,f3... const f1 = (callback) => setTimeout(()=>{ console.log('f1') callback() },1000) const f2 = (callback) =>setTimeout(()=>{ console.log('f2') callback() },1000) ... // 假设还有f3,f4...fn都是相似的函数,那么就要不断的把每一个函数写成相似的形式,而后使用下面的形式调用: f1(f2(f3(f4))) // 例子2:若是想给`fs.readFile`执行2个回调函数callback1,callback2 // 必须先包起来 const callback3 = ()=>{ callback1 callback2 } fs.readFile(filename,[encoding],callback3)
事件监听的含义是:采用事件驱动模式,让任务的执行不取决于代码的顺序,而取决于某个事件是否发生。先给出实现的效果:编程
const f1 = () => setTimeout(()=>{ console.log('f1') // 函数体 f1.trigger('done') // 执行完函数体部分 触发done事件 },1000) f1.on('done',f2) // 绑定done事件回调函数 f1() // 一秒后输出 f1,再过一秒后输出f2
接下来手动实现一下上面的例子,体会一下这种方案的原理:设计模式
const f1 = () => setTimeout(()=>{ console.log('f1') // 函数体 f1.trigger('done') // 执行完函数体部分 触发done事件 },1000) /*----------------核心代码start--------------------------------*/ // listeners 用于存储f1函数各类各样的事件类型和对应的处理函数 f1.listeners = {} // on方法用于绑定监听函数,type表示监听的事件类型,callback表示对应的处理函数 f1.on = function (type,callback){ if(!this.listeners[type]){ this.listeners[type] = [] } this.listeners[type].push(callback) //用数组存放 由于一个事件可能绑定多个监听函数 } // trigger方法用于触发监听函数 type表示监听的事件类型 f1.trigger = function (type){ if(this.listeners&&this.listeners[type]){ // 依次执行绑定的函数 for(let i = 0;i < this.listeners[type].length;i++){ const fn = this.listeners[type][i] fn() } } } /*----------------核心代码end--------------------------------*/ const f2 = () =>setTimeout(()=>{ console.log('f2') },1000) const f3 = () =>{ console.log('f3') } f1.on('done',f2) // 绑定done事件回调函数 f1.on('done',f3) // 多个回调 f1() // 一秒后输出 f1, f3,再一秒后输出f2
核心原理:数组
listeners
对象储存要监听的事件类型和对应的函数;on
方法时,往listeners
中对应的事件类型添加回调函数;trigger
方法时,检查listeners
中对应的事件,若是存在回调函数,则依次执行;和回调相比,代码上的区别只是把原先执行callback
的地方,换成了执行对应监听事件的回调函数。可是从模式上看,变成了事件驱动模型。promise
在刚刚事件监听的例子中,咱们改造了f1,使它拥有了添加监听函数和触发事件的功能,若是咱们把这部分功能移到另一个全局对象上实现,就成了发布订阅者模式:异步
// 消息中心对象 const Message = { listeners:{} } // subscribe方法用于添加订阅者 相似事件监听中的on方法 里面的代码彻底一致 Message.subscribe = function (type,callback){ if(!this.listeners[type]){ this.listeners[type] = [] } this.listeners[type].push(callback) //用数组存放 由于一个事件可能绑定多个监听函数 } // publish方法用于通知消息中心发布特定的消息 相似事件监听中的trigger 里面的代码彻底一致 Message.publish = function (type){ if(this.listeners&&this.listeners[type]){ // 依次执行绑定的函数 for(let i = 0;i < this.listeners[type].length;i++){ const fn = this.listeners[type][i] fn() } } } const f2 = () =>setTimeout(()=>{ console.log('f2') },1000) const f3 = () => console.log('f3') Message.subscribe('done',f2) // f2函数 订阅了done信号 Message.subscribe('done',f3) // f3函数 订阅了done信号 const f1 = () => setTimeout(()=>{ console.log('f1') Message.publish('done') // 消息中心发出done信号 },1000) f1() // 执行结果和上面彻底同样
若是认真看的话会发现,这里的代码和上一个例子几乎没有区别,仅仅是:async
on
方法更名为subscribe
方法,而且移到Message对象上trigger
方法更名为publish
,而且移到Message对象上这么作有意义吗?固然有。异步编程
如图:
消息中心的做用正如它的名字--承担了消息中转的功能,全部发布者和订阅器都只和它进行消息传递。有这个对象的存在,能够更方便的查看全局的消息订阅状况。
实质上,这也是设计模式中,观察者模式和发布/订阅者模式的区别。
Promise 是异步编程的一种解决方案,它由社区最先提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。
注意,只是在es6原生提供了Promise对象,不表明Promise的设计是在es6才出现的。最典型的,当咱们还在使用jquery
的$.ajax
时,已经使用$.ajax().then().catch()
时,就已经用到了Promise对象。所以这个也归为传统异步实现。
关于Promise详细内容,建议你们学习阮一峰老师的ES6教程,本文只介绍异步相关的核心内容。
接下来一样地,用js模拟实现一个简单的Promise对象。
首先分析Promise
的要点:
resolve(reject)
方法resolve
和reject
方法改变状态:resolve使状态从pending(进行中)
变成、fulfilled(已成功)
;reject使状态变成rejected(已失败)
then
方法用于注册回调函数,而且返回值必须为Promise对象,这样才能实现链式调用(链式调用是指p.then().then().then()这样的形式
)根据上述分析,实现一个有then
和resolve
方法的简单Promise
对象:
// 例子:手动实现简单Promise function MyPromise(fn){ this.status = 'pending' this.resolves =[] //存放成功执行后的回调函数 return fn(this.resolve.bind(this))// 这里必须bind,不然this对象会根据执行上下文改变 } // then方法用于添加注册回调函数 MyPromise.prototype.then = function(fn){ // 注册回调函数 并返回Promise. this.resolves.push(fn) return this } // resolve用于变动状态 而且触发回调函数,实际上resolve能够接受参数 这里简单实现就先忽略 MyPromise.prototype.resolve = function(){ this.status = 'fulfilled' if(this.resolves.length===0){ return } // 依次执行回调函数 并清空 for(i=0;i<this.resolves.length;i++){ const fn = this.resolves[i] fn() } this.resolves = [] //清空 return this } // 使用写好的MyPromise作实验 const f1 = new MyPromise(resolve=>{ setTimeout(()=>{ console.log('f1 开始运行') resolve() },1000) }) f1.then(()=>{ setTimeout(()=>{ console.log('f1的第一个then') },3000) }) // 一个小思考,下面函数的执行输出是什么? f1.then(()=>{ setTimeout(()=>{ console.log('f1的第一个then') },3000) }).then(()=>{ setTimeout(()=>{ console.log('f1的第二个then') },1000) })
以上就是Promise的核心思路。
本文针对传统的几种异步实现方案作了说明。而ES6中新的异步处理方案Generator
和async/await
会在后面补充。
若是以为写得很差/有错误/表述不明确,都欢迎指出
若是有帮助,欢迎点赞和收藏,转载请征得赞成后著明出处。若是有问题也欢迎私信交流,主页有邮箱地址
若是以为做者很辛苦,也欢迎打赏一杯咖啡~