Promise是es6中异步操作对象,在学习Promise之前我们首先要了解Javascript的一些有关异步操作、JS事件机制等方面的知识,这样才能更好的吸收今天所讲的内容。所以先从浏览器的进程讲起!
浏览器进程
浏览器是以多进程运行的,而我们的JS引擎是浏览器渲染进程中的一个线程(单线程),所谓的单线程就是一次只能执行一个任务,如果有多个任务需要处理的话,那么就需要排队,等待上一个任务执行完毕才会继续往下执行。这样就会有个弊端,就是如果当前执行的任务很耗时,那么等待的任务就会长时间的等待被执行,会出现一种假死(卡死)的状态,用户体验很不好。下面来看看浏览器都有哪些进程:
根据上面的图就很清楚的知道,我们平时写的JS代码是由JS引擎线程去处理的,而JS引擎线程是一个单线程,所以一次只能执行一个任务,为了改变这种不友好的用户体验,这时就会用到异步这个操作。所谓的JS异步并不是交由JS引擎去完成的,而是交给浏览器的其他线程去完成。JS异步操作还会涉及到JS事件循环机制。
JS事件循环机制
下图就是JS事件循环机制的一个执行流程:
所谓的JS事件循环机制其实可以这么理解,当JS引擎去执行JS代码的时候会从上至下按顺序执行,当遇到异步任务的,就会交由浏览器的其他线程去执行,如果是setTimeout/setInterval定时异步任务,浏览器的渲染进程就会开一个定时器触发线程去执行,当定时时间一到,就会通知事件触发线程将定时器的回调方法推送至事件任务队列的一个宏任务队列的列尾,等待JS引擎执行完同步任务后,再从事件任务队列中从头取出要执行的回调方法。其他异步任务也是这么一个流程。这就是所谓的JS事件循环。这其中还涉及到了宏任务和微任务的一个概念,那什么是宏任务?什么是微任务呢?
宏任务与微任务
下面的图会很清楚的告诉大家什么是宏任务及微任务:
我们发现今天要讲的Promise其实就是一个异步的微任务。宏任务与微任务的一个执行过程是这样的:
当JS引擎从任务队列中取出一个宏任务来执行,如果执行过程中有遇到微任务,那么执行完该宏任务就会去执行宏任务内的所有微任务。然后更新UI。后面就是再从任务队列中取出下一个宏任务来继续执行,以此类推。这是ES6的常考的一个知识点,希望大家一举拿下。
到这里或许大家会有些迷,不过不要紧,多看几次,或者去查些资料,争取拿下它。前面讲了那么多主要是将下面要讲的Promise贯穿起来,形成一个知识体系。而不是片面的了解它,或者只了解一部分,搞不清楚它是怎么串起来的。这也是我在百度上发现的一个痛点,很多知识点你搜的时候大部分文章讲的只有一部分,而且不同的文章得出的结论还不一样,所以我经过查阅大量的资料才总结出上面的这个知识点,目的只有一个,就是要把要学的东西弄清楚,知根知底那才是真的学到。
上面所讲的知识点可以总结为:浏览器的运行是靠几个进程相互配合工作的,JS引擎是浏览器渲染进程中的一个线程,它属于单线程,一次只能执行一个任务,如果有多任务则需要排队等待被执行,如需要执行异步操作就需要借助浏览器的其他线程完成,异步执行完成后将回调函数推送至任务队列中,待同步任务执行完毕后,再从任务队列中取出异步回调方法执行。任务队列中还可分为宏任务和微任务,如果宏任务中有微任务,那么当执行完宏任务,就立即执行微任务,然后才是更新UI。
Promise
Promise 是异步编程的一种解决方案,比传统的解决方案 (回调函数和事件)更合理和更强大。我们可以简单的把它理解为一个容器,它里面装的是一个异步操作(某个未来才会结束的事件)的结果。Promise操作后返回的对象还是一个新的Promise对象,所以支持链式调用,它可以把异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数,更便于理解与阅读。
Promise主要有以下特点:
1.Promise对象状态不受外界影响,它有三种状态:
pending:进行中
fulfilled:已成功
rejected:已失败
只有异步操作的结果才能确定当前处于哪种状态,任何其他操作都不能改变这个状态。这也是Promise(承诺)的由来。
2.Promise状态一旦改变就不会再变,任何时候都可以得到这个结果。它的状态改变只有两种结果:
1、从pending状态变为fulfilled状态
2、从pending状态变为rejected状态
只要有其中一种情况发生,状态就凝固了,不会再变,会一直得到这个结果,后续再添加Promise的回调函数也只能拿到前面状态凝固的结果
Promise缺点:
1.无法取消Promise,一旦新建它就会立即执行,无法中途取消
2.如果不设置回调函数(没有捕获错误),Promise内部抛出的错误,不会反应到外.
3.当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)
讲解Promise还会贯穿异步、同步、串行、并行等这几个慨念:
异步:多个任务可以同时执行,各不影响。
同步:多个任务必须排队,等待一个一个的执行。
串行:任务按顺序执行,前一个任务的结果可以是下个任务的参数,所以必须等待前一个任务执行完毕才能往下继续。
并行:多个任务可以同步执行,各不相关,实际就是一个异步操作的过程。
Promise API
由上图知,Promise既是一个对象也是一个构造函数,下面就具体分析它的每个api:
Promise.prototype.constructor()
它的基本用法如下:
Promise接收一个函数作为参数,函数里有resolve和reject两个参数,这两个参数其实是Promise内置的两个方法,会在异步操作执行结束后调用,可以将异步操作的结果回传至回调函数,以确定Promise最终的一个状态(是fulfilled还是rejected)。
resolve方法的作用是将Promise的pending状态变为fulfilled,在异步操作成功之后调用,可以将异步返回的结果作为参数传递出去。
reject方法的作用是将Promise的pending状态变为rejected,在异步操作失败之后调用,可以将异步返回的结果作为参数传递出去。
他们之间只能有一个被执行,不会同时被执行,因为Promise只能保持一种状态。
Promise.prototype.then()
Promise实例确定后,可以用then方法分别指定fulfilled状态和rejected状态的回调函数。它的基本用法如下:
then(onfulfilled,onrejected)方法中有两个参数,两个参数都是函数,第一个参数执行的是resolve()方法(即异步成功后的回调方法),第二参数执行的是reject()方法(即异步失败后的回调方法)(第二个参数可选)。它返回的是一个新的Promise对象。
一般推荐使用第二种写法,第二种写法能达到第一种写法的效果,但是二种写法还可以捕获onfulfilled执行过程中抛出的错,而第一种写法的onrejected方法无法捕获onfulfilled中抛出的错,具体代码如下:
如果Promise内部抛出的错不被捕获是不会反应到外部的:
then还可以很好的解决异步串行的操作,避免了传统异步串行操作层层嵌套的问题:
异步串行操作,主要思想是使各异步操作按一定顺序来执行,可以使用前一个异步操作的结果作为后一个异步的参数,严格确保前一个异步执行完毕后,再执行下一个异步
Promise.prototype.catch()
catch方法是.then(null,onrejected)的别名,用于指定发生错误时的回调函数。作用和then中的onrejected一样,不过它还可以捕获onfulfilled抛出的错,这是onrejected所无法做到的:
它的多种写法如下:
Promise错误具有"冒泡"的性质,如果不被捕获会一直往外抛,直到被捕获为止:
他们都只能捕获前面Promise抛出的错,而无法捕获在他们后面的Promise抛出的错: