Promise的实现原理以及相关用法

611 阅读6分钟

前言:本文主要是学习Promises相关用法和实现原理,通过对promise的实现来了解起原理,加强理解。

1 JS的同步和异步

  • 首先,对于js来说,js语言的运行环境是单线程的,也就是说一次只能完成一个任务,也就是只存在一条流水线,如果有多个任务需要进行就必须排队,等待前面一个任务完成再进行下一个任务。

1.1什么是同步和异步?

为什么要单线程?

  • JavaScript的单线程与他的用途有关。作为浏览器脚本语言,JS的主要用途是和用户互动,以及操作DOM。这就决定了它只能是单线程,否则就会带来很多复杂的同步问题。

  • 比如说,假定JS同时有两个线程,一个线程在某个DOM节点添加内容,另一个线程删除这个节点,这是就会出现矛盾,浏览器应该以哪个线程为准?

  • 所以为了避免复杂性,从一诞生,JS就是单线程。为了利用多核CPU的计算能力,HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。所以,这个新标准并没有改变JavaScript单线程的本质。

同步和异步的定义:

  • JavaScript 异步表示async,指:代码执行不按顺序,跳过执行,待其他某些代码执行完后,再来执行,称为异步

  • JavaScript同步表示sync,指:代码依次执行,称为同步

简单的理解:

  • 可以改变程序执行顺序的操作就可以看成为异步操作。其实异步和同步的差别很简单,就在于这条流水线上各个流程的执行顺序不同。

异步的运行机制:

(1) 所有的任务都在主线程上执行,形成一个执行栈Execution context stack

(2) 主线程之外,还存在一个任务队列Task queue。只要异步任务有了结果,就在任务队列之中放置一个事件。

(3) 一旦执行栈中的所有同步任务执行完毕,系统就会读取任务队列,看看里面有哪些事件,事件对应异步任务,于是结束等待状态,进入执行栈,开始执行任务队列中的任务

(4) 主线程不断重复上面第三步,只要主线程空了,就会读取任务队列,这就是JavaScript的运行机制,这个过程会不断重复。

1.2 同步异步实际体验

执行顺序为:

从中可以看出,就算事件设置0ms,也会先执行主线程上的任务,即1和5;setTimeout 的函数为异步任务,不进入主线程,而进入任务队列。程序先执行同步里的内容,执行完毕后,任务队列开始通知主线程,请求执行任务,该任务才会进入主线程来执行。

2 Promise的用法

作用:利用promise可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,promise对象提供统一的接口,使得控制异步操作更加容易。

2.1 Promise的三个状态

  • Pending:Promise的初始状态,等到任务完成或者被拒绝;
  • Resolved:执行完成并且成功的状态;
  • Rejected:执行完成并且失败的状态。

2.2 Promise对象必须实现then方法

  • then是promise的核心,而且then方法必须返回一个promise对象,同一个promise对象可以注册多个then方法,并且回调的执行顺序和他们注册的顺序一致。
  • then方法接收两个回调函数,他们分别是成功时的回调和失败时的回调:

  • value值表示的是异步执行成功之后在promise函数中获取的值,不确切的说就是可以获取该函数的私有变量,将promise的值获取过来之后在then中可以实现值的相应应用。
  • 其中,value值表示的是异步执行成功之后在promise函数中获取的值,不确切的说就是可以获取该函数的私有变量,将promise的值获取过来之后在then中可以实现值的相应应用。

3 那如何理解Promise?

举个具体的例子吧,某天,突发奇想,发了封邮件给木匠师傅,定制一个如此这般的家具。

木匠有求必应,即是说,邮件一旦发出就得到了他的承诺(Promise):在下一定尽力。

邮件中规定好了结果的通知方式:

成功了,直接将家具(res)邮递(resolve)过来。

失败了,直接将失败的信息(err)发邮件(reject)过来。

邮件发出等价于得到木匠的承诺P,之后,能做的只有等待(then)。

3.1 Promise 的行为特征

3.1.1 状态

每个Promise有三种状态:进行中(pending)、已成功(resolved)和已失败(rejected)。

一旦变成结果状态,即更改成resolved/rejected,状态便被冷冻,不能再被更改。

状态容器

  • Promise实质是个状态容器。 得到结果状态后,任何时候都可以访问到此状态。这与事件订阅通知不同,如果订阅发生在通知之后,订阅是不起作用的。

状态不可控

  • 一旦创建Promise,便会立刻执行,无法取消。处于pending状态时,无法得知进程具体的信息,比如完成百分比(虽然可以自行设置回调进行通知)。

失败的状态

  • 成功的状态只能resolve方法转成。 失败的状态可以由reject方法转成,也可以由抛出错误间接转成。三者都会正常的打印出失败的信息。

错误的报告机制

如果失败状态没有接收失败的回调函数接收,Promise会抛出错误。 这里的抛出错误,仅仅是在控制台显示之类的提示,不会终止程序的进程。

3.1.2

传入方法

创建Promise的同时也会执行传入方法。

传入方法不会因为调用了resolve/reject便终止执行,所以更优的方式是retrun resolve/reject。

回调方法

立即得到结果的Promise,其回调函数依然会晚于本轮事件执行。

这种后执行不同于setTimeout的将执行函数push到执行栈,而是将执行函数放到本轮的末尾。

结果参数

传入reject的参数,一般是字符串或Error实例,表示抛出的错误。

传入resolve的参数,一般是相应的JSON数据等,表示得到的数据。

传入resolve的参数,还可以是另一个Promise实例。

这时,只有当内层的Promise结束后,外层的Promise才会结束。

总结

Promise 是针对js中的异步问题提供的一种数据容器,为后续的知识学习做了铺垫。


参考文章:

JS异步编程之Promise详解

js中的同步和异步