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();

作者:rccoder

原文:https://github.com/rccoder/blog/issues/17

JavaScript 中回调地狱的今生前世相关推荐

  1. 彻底理解JavaScript中回调函数 (推荐)

    在javascript中回调函数非常重要,它们几乎无处不在.像其他更加传统的编程语言都有回调函数概念,但是非常奇怪的是,完完整整谈论回调函数的在线教程比较少,倒是有一堆关于call()和apply() ...

  2. 【JavaScript】回调地狱、Promise

    文章目录 1. 回调函数 2. 异步任务 3. 回调地狱 4. Promise 4.1 Promise定义 4.2 Promise基础用法 4.2.1 生成Promise实例 4.2.2 Promis ...

  3. 有关JavaScript中回调函数的所有内容!

    作者:Shadeed 译者:前端小智 来源:dmitripavlutin 点赞再看,微信搜索**[大迁世界],B站关注[前端小智]**这个没有大厂背景,但有着一股向上积极心态人.本文 GitHub h ...

  4. javascript中回调函数的理解和应用

    一. 什么是回调函数? 被作为实参传入另一函数,并在该外部函数内被调用,用以来完成某些任务的函数,称为回调函数. function f1(fn) {let a =1;console.log(a,'a' ...

  5. 理解与使用Javascript中的回调函数

    在Javascript中,函数是第一类对象,这意味着函数可以像对象一样按照第一类管理被使用.既然函数实际上是对象:它们能被"存储"在变量中,能作为函数参数被传递,能在函数中被创建, ...

  6. 【JavaScript】理解与使用Javascript中的回调函数

    在Javascript中,函数是第一类对象,这意味着函数可以像对象一样按照第一类管理被使用.既然函数实际上是对象:它们能被"存储"在变量中,能作为函数参数被传递,能在函数中被创建, ...

  7. 回调地狱以及解决回调地狱 - promise嵌套变链接 - 解决终极办法 - async 和 await

    回调函数? 当一个函数被当做参数传递时,这个函数就叫做回调函数-  callback 通常使用回调函数来处理异步代码 当异步代码执行结束后,需要执行的代码就要放在回调函数中 回调地狱? 当回调函数嵌套 ...

  8. 什么是回调地狱?如何解决回调地狱

    一.什么是回调地狱呢? 地狱这个词不陌生吧!对,没错就是那个十八层地狱的地狱,一层一层的地狱. 1.同步API,异步API的区别 这个问题呢,需要从Node.js的API说起,这里就会有人问了?博主你 ...

  9. JavaScript中的回调地狱及解决方法

    JavaScript中的回调地狱及解决方法 1.回调地狱 在使用JavaScript时,为了实现某些逻辑经常会写出层层嵌套的回调函数,如果嵌套过多,会极大影响代码可读性和逻辑,这种情况也被成为回调地狱 ...

最新文章

  1. 透视映射和射影映射的关系 Perspective and Projectivity
  2. 深入了解SpringCloud Hystrix
  3. 2.深度学习练习:Logistic Regression with a Neural Network mindset
  4. Android模块化之MicroModule(微信Pins工程)
  5. 初步的 MPI消息传递函数
  6. LINQ to SQL之使用Lambda Expression批量删除数据
  7. 计算机grand,The Grand
  8. 人工智能十大算法_中兴通讯人工智能白皮书(附下载)
  9. unity编辑器莫名崩溃
  10. 搜索引擎的概念鄂州_搜索引擎的基本概念
  11. 2018服务商口碑榜Top50(4月)重磅出炉
  12. 计算机内部数据的传输 进制,计算机内部数据加工处理和传送的形式是什么
  13. Vue ui/vue create创建项目报错:Failed to get response from https://registry.npmjs.org/vue-cli-version-ma
  14. C#写的ActiveX弹出窗口在Delphi程序中的应用时出现了问题。
  15. Linux——进程间通信
  16. 基于javaweb+mysql的个人日记管理系统
  17. 招生考试之友2017文科理科
  18. 迅视财经 买买买却维权难?明星主播不能装无辜
  19. 华为手机备份app从内部恢复
  20. CBoard框架使用总结七--添加首页图表

热门文章

  1. style和theme:一组属性值
  2. Android启动(Booting)
  3. Some Tips About Layout Resource
  4. Android---自定义Toast
  5. Handler 基本用法--线程间传值
  6. Python多线程下载网络URL图片的方法
  7. RabbitMQ—性能测试
  8. pycharm 进行远程服务器修改与调试
  9. Python的DEBUG LOG
  10. MyBatis学习与使用(一)