继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

JavaScript 中回调地狱的今生前世

拉丁的传说
关注TA
已关注
手记 572
粉丝 126
获赞 789

原文链接


1. 讲个笑话

JavaScript 是一门编程语言

2. 异步编程

JavaScript 由于某种原因是被设计为单线程的,同时由于 JavaScript 在设计之初是用于浏览器的 GUI 编程,这也就需要线程不能进行阻塞。

所以在后续的发展过程中基本都采用异步非阻塞的编程模式。

简单来说,异步编程就是在执行一个指令之后不是马上得到结果,而是继续执行后面的指令,等到特定的事件触发后,才得到结果。

也正是因为这样,我们常常会说: JavaScript 是由事件驱动的。

3. 异步实现

用 JavaScript 构建一个应用的时候经常会遇到异步编程,不管是 Node 服务端还是 Web 前端。

那如何去进行异步编程呢?就目前的标准以及草案来看,主要有下面的几种方式:

  • 回调

  • promise

  • Generator

  • await/async

3.1 回调

这种异步的方式是最基础的实现,如果你曾经写过一点的 Node, 可能经常会遇到这样的代码:

connection.query(sql, (err, result) => {    if(err) {        console.err(err)
    } else {
        connection.query(sql, (err, result) => {            if(err) {                console.err(err)
            } else {
                ...
            }
        })
    }
})

如此,connection.query() 是一个异步的操作,我们在调用他的时候,不会马上得到结果,而是会继续执行后面的代码。这样,如果我们需要在查到结果之后才做某些事情的话,就需要把相关的代码写在回调里面,如果涉及到多个这样的异步操作,就势必会陷入到回调地狱中去。

这种回调地狱不仅看起来很不舒服,可读性比较差;除此之外还有比较重要的一点就是对异常的捕获无法支持。

3.2 Promise

Promise 是 ES 2015 原生支持的,他把原来嵌套的回调改为了级联的方式。

一般着,我们对一个 Promise 可以这样写:

var a = new Promise(function(resolve, reject) {
  setTimeout(function() {
      resolve('1')
  }, 2000)
})
a.then(function(val) {    console.log(val)
})

如果要涉及到多个异步操作的顺序执行问题,我们可以这样写:

var a = new Promise(function(resolve, reject) {
  setTimeout(function() {
      resolve('1')
  }, 2000)
})

a
  .then(function(val){    console.log(val)    return new Promise(function(resolve, reject) {
      setTimeout(function() {
          resolve('2')
      }, 2000)
    })
  })
  .then(function(val) {    console.log(val)
  })

也可以把函数抽离出来

var a = new Promise(function(resolve, reject) {
  setTimeout(function() {
      resolve('1')
  }, 2000)
})function b(val) {  console.log(val)  return new Promise(function(resolve, reject) {
    setTimeout(function() {
        resolve('2')
    }, 2000)
  })
}

a.then(b).then(function(val) {console.log(val)
})

我们只需要 return 一个 Promise 即可实现这种多个异步操作的顺序执行。

粗略来看,这是一个比较优雅的异步解决方案了,并且在 Promise 中我们也可以实现分级的 catch

但对于之前接触过其他语言的同学来说还是比较别扭的。那能否用同步的方式来书写异步呢?

3.3 Generator

在 ES 2015 中,出现了 Generator 的语法,熟悉 Python 的同学肯定对这种语法有点了解。

简单来说,Generator 可以理解为一个可以遍历的状态机,调用 next 就可以切换到下一个状态。

在 JavaScript 中,Generator 的 function 与 函数名之间有一个 *, 函数内部使用 yield 关键词,定义不同的状态。

先看一段代码:

function a() {  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(1)
    }, 2000)
  });
};var b = co(function *() {    var val = yield a();
    console.log(val)
})

b()

上面的这段代码是借助 TJ 的 co 实现的,依照约定,co 中 yield 后面只能跟 Thunk 或者 Promise.

co 的实现代码很短,简单来说大体是这样:

// http://www.alloyteam.com/2015/04/solve-callback-hell-with-generator/function co(genFun) {    // 通过调用生成器函数得到一个生成器
    var gen = genFun();    return function(fn) {
        next();        function next(err, res) {            if(err) return fn(err);            // 将res传给next,作为上一个yield的返回值
            var ret = gen.next(res);            // 如果函数还没迭代玩,就继续迭代
            if(!ret.done) return ret.value(next);            // 返回函数最后的值
            fn && fn(null, res);
        }
    }
}

简单来说就是一直借助 generator 的 next 进行迭代,直到完成这个异步操作才返回。当前人家官方的 co 是 200 行代码,支持异步操作的并行:

co(function *() {    var val = yield [        yield asyn1(),        yield asyn2()
    ]
})()

但如果我们使用 co,强迫症们就会觉得这不是标准的写法,有点 hack 小子的感觉。

幸运的是,在 ES 2016 的草案中,终于提出了标准的写法。

3.4 await/async

这是在 ES 2016 中引入的新关键词,这将在语言层面彻底解决 JavaScript 的异步回调问题,目前可以借助 babel 在生产环境中使用。使用 await/async 可以让异步的操作以同步的方式来写。

使用方法和 co 非常类似,同时也支持同步写法的异常捕获。

function a() {  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(1)
    }, 2000)
  })
}var b = async function() {  var val = await a()  console.log(val)
}

b()

如果上述的代码完全用 Promise 实现,极有可能是下面的代码:

function a() {  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(1);
    }, 2000);
  });
};var b = function() {
  a().then(val) {    console.log(val)
  }  console.log(val)
};

b();

相比较来说,await/async 解决了完全使用 Promise 的一个极大痛点——不同Promise之间共享数据问题:

Promise 需要设定外层数据开始共享,这样就需要在每个then里面进行赋值,而 await/async 就不存在这样的问题,只需要以同步的方式去写就可以了。

await/async 对异常的支持也是特别好的:

function a() {  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(1)
    }, 2000)
  });
};var b = async function() {    try {        var val = await a()        console.log(val)
    } catch (err) {        console.log(err)
    }
};

b();


打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP