浅浅的谈一下回调地狱的问题

心路历程

之前编写c/c++的时候,真心不知道啥是回调地狱 , 为啥呢? 由于之前编程的时候 , 代码的编写顺序就是执行顺序。 好比我去读取一个文件 (代码简写)node

std::ifstream t;
t.open("file.txt");
buffer = new char(length);
t.read(buffer , length);
复制代码

在这里 , 代码执行到read的时候,会阻塞 , 直到文件读完,无论失败仍是成功都会有一个结果 , 这时候代码才会继续执行.
如今写js的时候 , 读取一个文件是这样的:c++

const fs = require('fs');
fs.readFile("file.txt",function(err , result){
    // 获取结果 , 执行相关的业务代码
})
code...
复制代码

能够看出,在js里,当执行读取文件的代码后,没有去等文件的执行结果,代码直接向下执行 , 当读取文件有结果的时候,在那个回调函数中执行相关的业务代码。
对于一个一直编写同步代码,秉承着面向对象就是上帝的程序员小白,看到这段代码心里是崩溃的😢程序员

1:这里代码的编写顺序居然不是代码的执行顺序!
2:调用函数 , 还能传一个函数为参数(难道是函数指针,可是为毛线要这样作,这都是什么鬼??!)
3:为何在那个回调函数里,能获取到读取文件结果的信息??
编程

什么是函数式编程 ??

简单说,函数式编程是一种“编程范式”,最直观的感受就是,函数是一种对象类型,能够做为参数传给别的函数,也能够做为结果return;
崩溃的我在学习js的路上停滞不前,内心一直鄙视这种js这种解释性语言,搞什么函数式编程,有毛线用??
直到有一天碰到了函数式编程的上帝 , 他语重心长的和我说:
咱们函数式编程天生是为并发编程而生的啊,你看看函数没有side effect,不共享变量,能够安全地调度到任何一个CPU core上去运行,没有烦人的加锁问题,多好啊。
如今想一想本身真的很小白 , 只知道面向对象编程 , 面向过程编程 , 对函数式编程彻底无感。。设计模式

爱上了函数式编程的我又遇到了新的麻烦 (回调地狱)

在编写js的过程当中, 事件获得了普遍的应用,配合异步I/O,将事件点暴露给业务逻辑。
事件的编程方式具备轻量级,松耦合,只关注事物点等优点。
可是在多个异步任务的情景下,事件与事件之间如何独立,如何协做是一个问题。
在node中,多个异步调用的情景有不少.好比遍历一个目录promise

fs.readdir(path.join(__dirname,'..'),function(err , files){
    files.forEach(function(filename , index){
        fs.readFle(filename , function(){
            ....
        })
    })
})
复制代码

这是异步编程的典型问题 , 嵌套过深 , 最难看的代码诞生了... 对于一个对代码有洁癖的人 , 这个真心不能忍!!安全

目前我所知道回调地狱的解决方式

Promise并发

协程异步

eventEmitter(事件发布订阅模式)async

promise

promise , 感受就是把要执行的回调函数拿到了外面执行 , 使代码看起来很"同步"~
看下promise如何实现的吧

let promise = new Promise(function(resolve , reject){
    // 执行异步代码的调用 
    async(function(err , right){
        // 彻底是能够根据返回的数据 , 直接执行相应的逻辑 , 不过为了让代码看着"好看同步" , 决定把数据看成参数传递给外面,</br>
        去外面(then的回调函数里 , 或者catch的回调函数里)执行 
        // 根据返回的数据 , 来肯定该调用哪一个接口 
        if(right){
            resolve("data"); 
        }
        if(err){
            reject('err') 
        }
    })
})  
// 若是执行了resolve() , 就走到这里 
.then(function(data){
    coding..
})
//若是执行了reject , 就走到了这里 
.catch(function(err){
    coding..
})
复制代码

这里能够看出 , 调用异步代码以后 , 已经获取了返回的数据 。以后把获取的参数传递给then或者catch的回调函数,去执行相关的回调函数。

简单说 , 就是把回调函数拿到了外面执行 , 让代码看着'同步'

promise具体细节 Promise源码解析

协程

首先说下协程的定义 : 协程是一个无优先级的子程序调度组件 , 容许子程序在特定的地方挂起和恢复.
线程包含于进程,协程包含于线程。只要内存足够,一个线程中能够有任意多个协程,但某一时刻只能有一个协程在运行,多个协程分享该线程分配到的计算机资源。
协程要作的是啥 , 写同步的代码却作着异步的事儿。

什么时候挂起?? 什么时候恢复呢??

挂起 : 在协程发起异步调用的时候挂起
恢复 : 其余协程退出而且异步操做完成时。

Generator --> 协程在js中的实现

写个🌰先 :

function* generator(x){
    var a = yield x+2;
    var b = yield a+3;
    var c = yield b+2;
    return;
}
复制代码

最直观的感受: 当调用generator(1)时,其实返回了一个链表.每个单元里装一些函数片断 , 以yield为界线 , 向上面的例子
(x+2;) --> (a+3) ---> (b+2) ---> (return;);
每次都经过next()方法来移动指针到下一个函数片断,执行函数片断(eval) , 返回结果.

var gen = generator(2);
gen.next(); // 当调用next(),会先走第一个代码段 , 而后就不执行了 , 交出控制权 .直到啥时候再执行next(),会走下一个代码段.
复制代码

这里能够看出来 , 咱们彻底能够在每一个代码段都封装一个异步任务 , 反正在异步任务执行的时候 , 我已经交出了控制权 , js主线程的代码继续往下走 , 啥也不耽误 , 等到异步任务完成的时候, 通知我一下 , 我这边看看等到其余协程也都退出的时候 , 就调用next() , 继续往下走.. 这样下来 , 看看代码多"同步" , 是否是~~~
继续看下 , 当调用next("5")时 , 里面是能够传入参数 , 并且传入的参数是上一个yield的异步任务的返回结果 .
能够说这个特性很是有用,就像上面说的,当异步任务完成的时候,就再调用next() , 走下面的代码 , 可是无法获取到上一个异步任务的结果的 , 因此这个特性就是作这个的 , next('异步任务的结果');

async/awit

说到async/awit , 最直观的感受 , 不就是对gennerator的封装 , 改个名么??

let gen = async function(){      
    let f1 = await readFile("one");
    let f2 = await readFile2(123123);       
}
复制代码

简单说 , async/awit 就是对上面gennerator自动化流程的封装 , 让每个异步任务都是自动化的执行 , 当第一个异步任务readFile("one")执行完 , async内部本身执行next(),调用第二个任务readFile2(123123),以此类推...

这里也许有人会困惑 , 为何wait 后面返回的必须是promise ??

是这样 , 上面说了当第一个异步完成时通知我一下 , 我在调用next() , 继续往下执行 , 可是我何时完成的, 怎么通知你??
promise就是作这件事的 , async内部会在promise.then(callback),回调函数里调用 next()... (还有用Thunk的, 也是为了作这个事的);

eventEmitter(事件发布订阅模式)

事件发布订阅模式普遍应用于异步编程的模式,是回调函数的事件化 , 能够很好的解耦业务逻辑 , 也算是一种解决回调地狱的方式 , 不过和promise,async不一样的是 , promise,async就是为了解决回调地狱而设计出来的 , 而eventEmit是一种设计模式 , 正好能够解决这个问题~~

// 订阅 
emitter.on('event1',function(message){
    console.log(message);
})
// 发布
emitter.emit('event1',data);
复制代码

事件发布订阅模式能够实现一个事件与多个回调函数的关联,这些回调函数又称为事件监听器.听过emit发布事件后 , 消息会当即传递给当前事件的全部监听器执行.

事件发布订阅模式经常用来解耦业务逻辑,事件的发布者无需关注订阅的监听器如何实现业务逻辑 , 数据经过消息的方式灵活传递。

fs.readFile("file.txt",function(err , result){
    // 获取结果
    emmitter.emit('event1' , result)
})
emmiter.on('event' , function(result){
    // 在这里执行相关业务代码 
})
复制代码

事件发布订阅模式咋实现的呢 ???

简单说就是在上层维护一个私有的callback回调函数队列 , 每次emmit的时候都会遍历队列 , 把相应的事件拿出来执行。

总结

这里能够看出,不论是哪种处理回调地狱的方式 , 都是要处理回调函数的, 只不过是真正调用的位置不一样而已~ ,上面三种方式作的都是如何组织回调函数链的执行位置 , 如何让代码看着更好看 ~~

相关文章
相关标签/搜索