回调函数
思路
在调用栈中将回调函数作为参数传入异步函数,异步任务执行完后,调用这个回调函数
// 实现function f1(cb){setTimeout(function(){let data='数据'cb(data)},1000)}// 使用f2=function(data){console.log(data)}f1(f2)
优点
缺点
调用异步函数时需要向异步函数里面传递回调,代码耦合度太高,不利于代码维护;
嵌套的异步容易引起回调地狱
事件监听
思路
采用事件驱动的模式
在异步函数上挂上三个属性:
listeners:一个对象,用属性名记录事件,属性值是个数组,存放回调函数
on:往listeners放事件和回调函数
trigger:触发事件,执行listeners中的回调函数
在调用栈中调用 异步函数.on 绑定事件和回调
在异步函数里面触发事件即可
//实现const f1 = () => setTimeout(()=>{let data='数据'f1.trigger('done',data) // 执行完函数体部分 触发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,data){if(this.listeners&&this.listeners[type]){// 依次执行绑定的函数for(let i = 0;i < this.listeners[type].length;i++){const fn = this.listeners[type][i]fn(data)}}}/*----------------核心代码end--------------------------------*///使用const f2 =(data) =>{ console.log(data) }const f3 = (data) =>{ console.log(data) }f1.on('done',f2) // 绑定done事件回调函数f1.on('done',f3) // 多个回调f1()// 一秒后输出 f1, f3,再一秒后输出f2
优点
直接调用异步函数,无需传递回调,避免了直接使用回调的高耦合问题
缺点
由事件驱动,不容易看出执行的主流程;嵌套的异步容易引起回调地狱
发布订阅
思路
声明一个全局对象Message,把事件监听方案中挂在异步函数上的三个属性转移到Message上
Message:{
listeners:事件监听方案中的listeners,
subscribe:事件监听方案中的on,
publish:事件监听方案中的trigger
}
// 实现// 消息中心对象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 f1 = () => setTimeout(()=>{console.log('f1')Message.publish('done') // 消息中心发出done信号},1000)//使用const f2 = () => console.log('f2')const f3 = () => console.log('f3')Message.subscribe('done',f2) // f2函数 订阅了done信号Message.subscribe('done',f3) // f3函数 订阅了done信号f1() // 执行结果和上面完全一样
优点
在事件监听方案中,是使用异步函数绑定;在发布订阅方案中,是使用全局对象绑定,有这个全局对象的存在,可以更方便的查看全局的消息订阅情况
缺点
promise(ES6)
思路
实现一个promise对象,里面有三个状态pending,resolved,rejected和一个then方法,then方法会返回一个新的promise对象,这样就可以实现链式调用then方法;
当异步完成时,状态修改,自动执行then方法里面对应的回调;
如果回调里有嵌套的异步逻辑,就把嵌套的这个异步逻辑用新的promise包装一下返回,当这个嵌套的异步完成时,新的promise的状态修改,会自动执行后面then方法里面对应的回调;
这样就可以链式调用嵌套的异步了
//实现(手写promise)const f1 = new Promise(function (resolve, reject) {setTimeout(function(){resolve(100) // 承诺达成// reject(new Error('promise rejected')) // 承诺失败},1000)})//使用const f2 = () => setTimeout(()=>{consolelog('f2'); return 3})const f3 = () => console.log('f3')f1.then(function (value) {console.log('resolved', value)return new Promise((resolve,reject)=>{return f2})}, function (error) {console.log('rejected', error)}).then(function (value) {console.log('resolved', value)f3()}, function (error) {console.log('rejected', error)})
优点
缺点
generator(ES7)
思路
使用生成器函数可以暂停执行的特点,把promise链条截断,像使用同步代码一样使用异步函数
//实现const g=main()const result = g.next()result.value.then(data => {const result2 = g.next(data)if (result2.done) returnresult2.value.then(data => {const result3 = g.next(data)if (result3.done) returnresult3.value.then(data => {g.next(data)})})})// 上面的代码可以使用递归实现,如下:function co (generator) {const g = generator()function handleResult (result) {if (result.done) return // 生成器函数结束result.value.then(data => {handleResult(g.next(data))}, error => {g.throw(error)})}handleResult(g.next())}co(main)//使用function * main () {try {const users = yield ajax('/api/users.json')console.log(users)const posts = yield ajax('/api/posts.json')console.log(posts)const urls = yield ajax('/api/urls11.json')console.log(urls)} catch (e) {console.log(e)}}
优点
缺点
需要执行生成器函数生成生成器对象,然后嵌套的调用生成器对象的next方法
async/await(ES8)
思路
promise+generator+co的语法糖
async function main () {try {const users = await ajax('/api/users.json')console.log(users)const posts = await ajax('/api/posts.json')console.log(posts)const urls = await ajax('/api/urls.json')console.log(urls)} catch (e) {console.log(e)}}
