Promise是面试中最多见的问题之一,也是ES6中颇有用、很核心的一个新特性。尤为在现在异步操做愈来愈多、愈来愈复杂的状况下,Prmoise更是显示出了它强大而又优雅的本质。这篇文章,咱们来系统地讲解一下Promise相关的核心知识点。git
本文已同步至个人我的主页。欢迎访问查看更多内容!若有错误或不足,欢迎随时探讨交流。谢谢你们的关注和支持!面试
本文,咱们按照如下的思路来逐步深刻Promise:编程
Promise
是一种异步编程的解决方案,它能够将异步操做以同步的流程表达出来,它比传统的使用回调函数和事件来处理异步问题更加合理,更符合人们线性处理问题的逻辑。 从语法上说,Promise
是一个对象,它里面保存着一个未来才会发生的事情(通常是一个异步操做)的状态和结果。数组
听起来,有些抽象,全是概念性的东西。那接下来咱们看看为何ES6中会出现Promise
?经过具体示例,能够帮助咱们更好的理解什么是Promise
。promise
在ES6出现Promise以前,咱们要处理一个异步请求,一般是这样的:bash
// 利用回调函数来处理异步请求结果 // 不少异步请求方法也会设计一些事件,在事件中处理异步请求结果 asyncRequest(function(resData) { // 处理请求结果 }); 复制代码
这样看着没什么问题,但需求老是各类各样甚至是变态的。若是咱们须要在第一个请求返回结果后再发起第二个请求呢?再若是,第二个请求结果返回后后,咱们须要再发起第三个请求呢?以后,再是第四个...第五个......此时,代码应该会变成这样:markdown
asyncRequest1(function(resData1) { asyncRequest2(function(resData2) { asyncRequest3(function(resData3) { asyncRequest4(function(resData4) { asyncRequest5(function(resData5) { // ...... // 处理请求结果 }); }); }); }); }); 复制代码
这时,代码嵌套层次太深,再加上每次请求结束咱们应该还须要作一些适当的逻辑处理,这样每一个处理请求结果的地方还须要额外的代码,这样整个代码块显得很臃肿,一点也不优雅!最主要的是,这样的代码很容易出错,并且出错后不容易定位错误,阅读和维护起来十分费劲。异步
这就是异步编程最让人头疼和无语的地方:因为异步操做嵌套层次过深而致使的“回调地狱”!async
出现这种状况,就须要思考新的异步编程的处理方法。有没有什么方法能在知足上面例子的需求的同时又能解决这种嵌套式的回调地狱呢?能不能不使用嵌套式回调,而使用链式回调呢?确定是有的,这也就是Promise
出现的缘由。同时,能不能最好不使用回调的方式来处理异步请求呢?固然也是能够的,这就是咱们后面文章会讲的async/awiat
。异步编程
咱们来看看,上面的例子,若是使用Promise来实现是什么样子?应该是这样:
new Promise(asyncRequest1)
.then(asyncRequest2(resData1))
.then(asyncRequest3(resData2))
.then(asyncRequest4(resData3))
.then(asyncRequest5(resData4))
.catch(handleError(errorMsg))
复制代码
上面的例子,只有每一个一步请求asyncRequest
成功返回结果,才会进入下一个.then()
方法中,从而进行下一个异步请求......以此类推。当任何一个请求出错时,就会进入.catch()
方法中,能够在这里处理错误。这样的链式回调,既知足前面例子的需求,同时又避免了嵌套回调,从而避免了“回调地狱”的出现。
这里具体的语法看不懂,没关系!不要慌!这个例子只是为了说明Promise
是如何用链式回调来解决嵌套回调地狱的。接下来,咱们就来讲说如何使用Promise
,讲讲它的基本语法。
ES6中规定,Promise
是一个构造函数,能够用来实例化一个Promise
对象。下面是一个简单的例子:
// Promise构造函数接收一个函数做为参数 let promise = new Promise(Function); 复制代码
文章最开始介绍什么是Promise的时候说过:Promise
是一个对象,它里面保存着一个未来才会发生的事情(通常是一个异步操做)的状态和结果。
咱们先来看看Promise表明的异步操做的状态有哪几种?——一共只有三种状态:
这三种状态,不会共存,Promise
只会处于其中某一种状态。当异步请求开始而且未结束(没有返回结果)时,处于pending
状态。当异步请求返回结果后,能够根据请求返回的结果将Promise
的状态修改成fulfilled
或者rejected
。而且,一旦Promise
的状态第一次改变,就不再能更改成其它任何状态。因此,Promise
的状态改变过程只有两种状况:
那么,如何修改Promise
的状态呢?这就须要了解调用Promise
构造函数时,传递给构造函数的Function
参数了。Promise
会为这个函数设置两个参数,resolve
、reject
。这两个参数是两个函数,由JavaScript引擎提供,不用本身部署。
resolve()
函数,能够将Promise
的状态由pending
改变为fulfilled
。reject()
函数,能够将Promise
的状态由pending
改变为rejected
。
这里有两点须要注意的地方!!
Promise
内部只有用resolve()
、reject()
才能改变它的状态。return
任何值(包括一个Error实例)都不会改变它的状态。throw
任何值,还会引发报错!resolve()
、reject()
和return
的意义不一样。他们只是改变了Promise
的状态,并不会结束代码执行。也就是说resolve()
、reject()
以后的代码依旧会执行。(虽然不建议在他们后面再有代码出现)Promise
时,参数函数中异步操做以外的同步代码都会当即执行。来看一个示例,简单明了地理解上面的文字。
let promise = new Promise(function(resolve, reject) { // 下面两行代码会当即执行,不会等待异步操做结果返回、状态改变 let a = '123'; console.log(a); // '123' // 一个异步请求 asyncRequest(function(resData) { if (/* 异步操做成功 */){ // 将Promise的状态改成fulfilled(已成功) resolve(resData); // resData通常是异步操做的结果 } else { // 将Promise的状态改成rejected(已失败) reject(resData); // resData通常是一些错误信息 } }); }); 复制代码
上面的例子,在Promise
内部发起了一个异步请求,当请求完成,拿到返回值resData
时,咱们能够根据具体的业务需求修改Promise
的对应状态。
细心的同窗会发现,上面的例子中,咱们在resolve()
和reject()
函数中传入了参数resData
,这是在干什么?还记得么?Promise
不只能保存异步操做的状态,还能保存异步操做的结果。咱们将将异步操做的结果resData
传给这两个函数,就是将其保存到了Promise
对象中。
那么,Prmoise
对象中保存了异步操做的最终状态和结果,咱们如何获取呢?换句话说,咱们怎么知道异步操做的状态和结果分别是什么呢?
其实,每一个Promise
的对象实例都会有一个.then()
和.catch()
方法,这两个方法都接收一个函数做为参数,这个函数会被Promise
传入一个参数,这个参数就是传入resolve()
、reject()
方法中的异步请求的结果(上个例子中的resData
)。当Promise
内部状态变为fulfilled
时,就会进入.then()
方法中,执行里面的回调函数。同理,当Promise
内部状态变为rejected
时,就会进入.catch()
方法中,执行里面的回调函数。
/***接着上面例子***/ promise.then(function(resData) { // promise状态变为fulfilled,执行这里 console.log(resData); }).catch(function(resData) { // promise状态变为rejected,执行这里 console.log(resData); }); 复制代码
像上面这样,当执行进入.then()
中,就说明Promise
的状态是fulfilled
。进入.catch()
中,就说明状态是rejected
,通常会在这里进行错误处理。同时,异步操做的结果会被传入定义在.then()
、.catch()
内部的函数中,咱们能够直接访问使用。
**在.then()/.catch()
的返回值依旧是一个Promise
实例。**也就是说,在.then()/.catch()
中return
任何值,都会被转化成一个Promise
实例。因此.then()
后面能够链式继续调用.then()/.catch
,.catch()
后面一样也能够。因而,就有可能出现下面这样的代码:
// 这样的代码是彻底没有问题的。 promise.then(function(resData) { // 一些代码 }).then(function(resData) { // 一些代码 }).catch(function(error) { // 一些代码 }).then(function(resData) { // 一些代码 }).catch(function(error) { // 一些代码 }); 复制代码
这里有一些须要注意的地方!!
.then()
中return
任何值(包括一个Error
实例),都会进入后面最邻近的.then()
。.then()
中throw
任何值或者内部代码报错,都会进入后面最邻近的.catch()
。.catch()
中状况与.then()
彻底一致。Promise.resolve
方法接收一个任意值做为参数,能够将其转换为Promise对象。
该方法对参数的处理,能够分为如下四种不一样的状况:
此时,Promise.resolve
方法将不会作任何转换,直接原封不动的返回这个实例。
thenable
对象是指对象内部实现了then
方法的对象。此时,Promise.resolve
方法会先将该对象转换为Promise
对象,而后当即执行参数对象本身的then
方法。
最终,转换成的Promise
对象的状态彻底依赖于它内部then
方法的具体实现,不必定是fulfilled
状态,也有多是rejected
。
// 定义一个thenable对象 let thenable = { then: function(resolve, reject) { resolve(42); // 若是换成执行下面一行代码,后面将会进入.catch()中 // reject('error'); } }; let p1 = Promise.resolve(thenable); p1.then(function(value) { console.log(value); // 42 }).catch(function(value) { console.log(value); // 'error' }); 复制代码
若是参数不是thenable
对象,或者不是一个对象,Promise.resolve
方法返回一个新的Promise
对象,状态为fulfilled
,对象保存的值就是这个参数值。
Promise.resolve('foo') // 等价于 new Promise(resolve => resolve('foo')); let p = Promise.resolve('Hello'); p.then(function (s){ console.log(s); // 'Hello' }); 复制代码
Promise.resolve
方法容许调用时不带参数,直接返回一个fulfilled
状态的Promise
对象,对象保存的值为undefined
。
let p = Promise.resolve(); p.then(function (value) { console.log(value); // undefined }); 复制代码
Promise.reject
方法也会返回一个新的Promise
实例。不论传入的参数是什么数据类型,有没有thenable
方法,该实例的状态必定为为rejected
,且返回的Promise
对象中保存的值就是传入Promise.reject
方法时原封不动的参数值。
例子一 let p = Promise.reject('error'); // 等同于 let p = new Promise((resolve, reject) => reject('error')); // 例子二 let thenable = { then(resolve, reject) { /** * 不论执行下面的哪一行, * 最后Promise对象的状态都是rejected, * 都会进入.catch中 * reject('error'); // resolve('fulfilled'); } }; Promise.reject(thenable).then(data => { // 不会进入这里!! console.log('进入then!'); }).catch(e => { console.log('进入catch!'); // !注意!这里的e的值是传入Promise.reject()方法的thenable对象 console.log(e === thenable); // true }) 复制代码
Promise.all
方法用于将多个Promise
实例,包装成一个新的Promise
实例。
let p = Promise.all([p1, p2, p3]); 复制代码
Promise.all
方法接收一个数组做为参数,数组元素p1
/p2
/p3
都是Promise
实例。若是不是,就会先调用Promise.resolve()
方法,将参数转为Promise
实例,再进一步处理。
最终,p
的状态由p1
/p2
/p3
共同决定,分红两种状况:
p1
/p2
/p3
的状态都变成fulfilled
,p
的状态才会变成fulfilled
。此时p1
/p2
/p3
的返回值组成一个数组,传递给p
的回调函数。p1
/p2
/p3
之中有一个被rejected
,p
的状态就变成rejected
。此时第一个被reject
的实例的返回值,会传递给p
的回调函数。Promise.race
方法一样是将多个Promise
实例,包装成一个新的Promise
实例。
let p = Promise.race([p1, p2, p3]); 复制代码
Promise.race
方法接收的参数与Promise.all
方法同样,若是不是 Promise
实例,就会先调用Promise.resolve()
方法,将参数转为 Promise
实例,再进一步处理。
上面的例子中,只要p1
/p2
/p3
之中任意一个实例率先改变状态,不论变为哪一种状态,p
的状态就跟着改变。那个率先改变状态的Promise
实例的返回值,就传递给p
的回调函数。
《面试精选之Promise》——这篇文章也写得很好,本文参考借鉴了一些地方,推荐你们能够结合着一块儿看。