[译] 深刻理解 Promise 五部曲:1. 异步问题

原文地址:http://blog.getify.com/promis...javascript

在微博上看到有人分享LabJS做者写的关于Promise的博客,看了下以为写得很好,分五个部分讲解了Promise的前因后果。从这篇文章开始,我会陆续把五篇博客翻译出来跟你们分享,在大牛的带领下真正理解Promise。卖个关子,做者看待Promise的角度跟我一直以来看到的讲解Promise的角度彻底不同,不仅是定留在解决回调金字塔上,至少我没想到Promise居然有这么重要的意义。先上第一篇。java

在这篇文章中,我会解释咱们为何须要使用一个更好的方式(好比Promise)来进行异步流程的编写。编程

异步

你确定据说过Javascript中的异步编程,可是它究竟是什么呢?segmentfault

好比当你发生一个Ajax请求,你一般会提供一个回调函数,这个回调函数会在请求返回的时候被调用。可是你是否思考过你的回调函数在其余代码也须要运行的时候是如何被调用的呢?若是两个回调函数同时都要运行会怎样呢?JS引擎会如何处理这个问题呢?promise

为了理解异步究竟是什么,你首先须要理解一个问题:JS引擎是单线程的。这意味着在任何环境中,只有一段JS代码会被执行。可是什么叫一段JS代码呢?总的来讲,每一个函数是一个不可分割的片断或者代码块。当JS引擎开始执行一个函数(好比回调函数)时,它就会把这个函数执行完,也就是说只有执行完这段代码才会继续执行后面的代码。并发

换句话说,JS引擎就像一个主题公园中的游乐项目,这个项目每次只能一我的玩儿,人们会排成一个长长的队。你们一个个上去玩儿,下来一个而后再上去一个。若是你要玩儿这个项目你只能在队尾排队等待。幸运的是,每一个人都很快就下来了,因此这个队伍移动得很快。异步

上面说的队伍在技术上被叫作事件轮询。它尽量快的进行轮询,若是事件队列中有代码须要执行,它会让JS引擎执行这段代码,而后移到下一个须要执行的代码,或者等待新的代码进来。异步编程

并发

若是程序在一个时间只有一个任务在执行,这样明显是低效并且有限制性的。若是你点击一个按钮提交一个表单,而后你的鼠标就会被冻结而且你不能滚动页面,这个状况会持续几秒直到请求返回,这样确定会带来不好的用户体验。函数

这就是为何真实的程序会有不少任务在运行而不是就只有一个任务,可是JS引擎是怎么在单线程的环境下实现的呢?编码

你应该想到每一个代码块运行只要很短的时间,一般不到1毫秒。你一眨眼的时间,JS引擎会执行上千百个这样的代码块。可是并非全部的代码块都是为了执行同一个任务。好比,当你点击提交按钮以后,你也能够点击导航或者滚动页面等等。每一个任务都会被分为不少个原子操做,执行这些原子操做会很是快。

好比:

Task A

  • step1

  • step2

  • step3

  • step4

Task B

  • step1

  • step2

JS引擎确定不能在执行A:1步骤的同时执行B:1。可是Task B不须要等到Task A执行完后再执行,由于引擎能够在每一个独立的原子操做之间快速的切换,多是按下面的顺序执行的:

  • A:1

  • B:1

  • A:2

  • B:2(Task B完成)

  • A:3

  • A:4(Task A完成)

因此,事实上Task A和Task B是能够"同时"运行的,经过穿插地执行它们的每一个原子操做,这叫作并发,换句话说,Task A和Task B是并发的。

咱们很容易就会把并发和并行弄混。在真正并行的系统中,你会有多个线程,可能一个线程执行Task A同时另外一个线程执行Task B。这也意味着,A:1的运行不会阻塞B:1的运行。这就好像有主题公园中有两个分开的游乐项目,会有两队人在排队,它们互相不影响。

JS事件轮询是一个简单的并发模型。它只容许把每一个事件添加到事件队列的队尾,而这个队列是先进先出的。当条件容许时,回调函数就会被运行。

同步状况下的异步

在JS中编写异步代码一个巧妙可是烦恼的问题是JS引擎实际执行代码的方式跟咱们看上去不大同样。例如:

makeAjaxRequest(url,function(response){
    alert("Response:" + response) ;
}) ;

你会怎么描述这段代码的流程呢?大多数开发者大概会这么说:

  1. 发送Ajax请求

  2. 等到请求完成的时候,弹出提示框

可是这跟JS引擎实际的执行状况相比还不够准确。这个问题主要是由于咱们大脑习惯同步的方式。在上面这个描述中,咱们使用“等到。。。的时候”来解释,这就也是说咱们会阻塞等待Ajax请求,而后继续执行后面的程序。

JS在步骤1和步骤2之间不会阻塞。一个更准确的描述上面这段代码的方式是:

  1. 发送Ajax请求

  2. 注册回调函数

  3. 继续向下执行

  4. 在将来某个时间点,惊呼“Oh,我刚才获得一个返回!”。如今,返回去执行注册的那个回调函数。

这两个解释的区别彷佛没什么大不了的,可是咱们跳过第三步的思考方式是一个大问题。

源代码是给开发者的而不是计算机的。计算机只关心1和0.有无限种程序能产生同样的1和0序列。咱们编写源代码为了使得咱们可以以一种有含义而且准确的方式理解代码是干吗的。因为咱们的大脑很难处理异步,因此咱们须要找出一种更加同步的方式来编写异步代码,隐藏具体的异步实现。

例如,若是下面这段代码能像咱们须要的那样运行而且不会阻塞,那么它是否是更好理解了呢?

response = makeAjaxRequest(url) ;
alert("Response:" + response) ;

若是咱们能够像这样编码,那么咱们就能够隐藏或者抽象makeAjaxRequest()的异步本质,不须要担忧具体细节。
换句话说,咱们能使得异步代码只出如今具体的实现上,把这些烦人的东西埋在属于它的地方。

总结

咱们尚未解决问题。可是至少咱们知道了问题是什么:用异步的方式来表达异步的代码是艰难的,甚至很难用咱们的大脑来理解。

咱们须要的只是一种以同步的代码来尽量隐藏具体的异步实现的方式,这样咱们的大脑更好理解。咱们的目标是以同步的方式来编码而不须要关系它的实现的同步仍是异步。

在第二部分:转换的问题中,我会着手处理“回调地狱”来解释这些问题,咱们也将看到Promises是如何搞定它的。

深刻理解Promise五部曲--1.异步问题
深刻理解Promise五部曲--2.转换问题
深刻理解Promise五部曲--3.可靠性问题
深刻理解Promise五部曲--4.扩展性问题
深刻理解Promise五部曲--5.乐高问题

最后,安利下个人我的博客,欢迎访问:http://bin-playground.top

相关文章
相关标签/搜索