上节课我们介绍了Promise
中的常用API
,这节课我们来展开说一说Promise
中的几个关键问题,这几个关键问题是为了后面封装方面的内容来做基础支撑的。
修改Promise
状态
首先第一个问题,我们怎么修改Promise
对象的状态?我在之前的课程中也已经说过了,我们不能够手动去修改Promise
对象的状态,但是是不是没法修改呢?当然不是了。或许大家有人学过Java
,知道Java
中的属性有一种权限叫private
,这个权限不允许我们手动去直接修改属性,但是我们会在类中预留一个方法,然后我们通过这个方法来去修改或者读取某个private
的属性。
在Promise
中也做了类似的设计。就是我们之前一直再用的resolve
函数和reject
函数。
resolve()
使得Promise
对象状态从pending
=>fulfilled
reject()
使得Promise
对象状态从pending
=>rejected
但是出了这两种方法还有没有其他方法来改变Promise
对象的状态呢?其实是有的。我们来看一下:
let p = new Promise( (resolve, reject) => { throw "Error"; } ); console.log(p);
我们来看一下这段代码,我们在Promise
中直接抛出一个异常,那么结果会是什么样子呢?我们来看一下:
我们看到控制台中输出的Promise
对象的状态是rejected
,而结果就是我们抛出的异常字符串。羡慕这个报错我们也说过,这是因为我们没有指定失败回调导致的。
所以说Promise
对象的状态可以通过以上三种方法来修改。
Promise
能否执行多个回调
我们在初始化了Promise
对象之后,都会来指定相应的回调函数,那么回调函数我们都是在哪指定的呢?我们上节课中在介绍到then
方法和catch
方法的时候就说到了,这两个方法是用于指定回调函数的,所以说我们的所有回调函数都是在then
或者catch
方法中来进行指定的。那么如果我给一个Promise
对象指定了多个回调函数,那么这些回调函数会都被执行吗?我们来试一下:
let p = new Promise( (resolve, reject) => { resolve(); } ); p.then(()=>console.log("Callback 01")); p.then(()=>console.log("Callback 02"));
我们现在这段代码是让这个Promise
对象是一个成功的Promise
,然后我们接下来指定了两个成功的回调,那么我们来看一下结果是怎样:
我们来看一下,控制台中成功输出了两个回调的执行结果,所以说当我们给一个Promise
对象制定了多个回调的话,那么对应状态的回调都会被执行。
Promise
状态修改和指定回调的先后顺序
我接下来再来思考一个问题,我们在Promise
对象的状态修改和回调指定到底谁先执行呢?我们通常通过new Promise(excutor)
来实例化一个Promise
对象,我们基本都是在excutor
函数中执行异步任务,并调用resolve
或者reject
函数来修改状态,而我们是在实例化Promise
对象之后再调用then
或者catch
来指定回调函数。
但是Promise
对象的状态修改和指定回调的先后顺序真的就像我们上面所说的那样吗?有没有可能回调函数的指定在状态修改之前呢?其实是有可能的。而且先指定回调函数再修改状态,和先改变状态再指定回调函数这两种情况都是存在的。
既然两种情况都会存在那么什么时候会先改变状态,什么时候又会先指定回调呢?薛定谔的嘛?当然不是,当我们在excutor
中直接调用resolve
或者reject
函数时这将是一个同步任务,这个时候会先改变状态。状态改变之后马上指定回调,然后根据状态调用回调函数获取到任务执行完成后的数据。
如果我们在excutor
中开启一个异步任务的话,那么我们将会先指定回调函数,等异步任务执行完成后,状态修改,然后调用回调函数,获取到任务执行完后的数据。
then
方法返回的新Promise
对象
我们之前提到过,then
方法在被调用之后是会生成一个新的Promise
对象的。但是这个新的Promise
对象的状态还有结果都是由什么决定的呢?接下来我们就来展开说一说。
首先我来看一下代码:
let p = new Promise( (resolve, reject) => { resolve(); } ); let result = p.then( () => console.log("Callback 01") ); console.log(result);
我们这段代码干了什么?首先我们实例化了一个成功的Promise
对象,然后在then
方法中指定了成功时的回调函数,但是我的关注点不在这,我们关注点是我们声明一个变量来接收then
方法的返回值,并打印出来。
我们来看,控制台输出了一个对象。这正如我们之前说的会返回一个新的Promise
对象,但是这个对象的状态和结果都是什么呢?其实then
方法返回的Promise
对象状态和结果都由执行的回调函数来决定。
什么意思呢,就是说不管then
方法中指定的成功的回调以及失败的回调哪一个被执行,那么被执行的回调函数的执行结果将决定then
方法返回的Promise
对象的状态和结果。
那么回调函数执行的结果有几种情况呢?
- 抛出异常
- 返回非
Promise
类型的结果 - 返回
Promise
对象
可能有人要说还有没有返回值的情况,但是我们都知道js
函数没有返回值代表着返回undefined
,这也属于非Promise
类型的结果。所以说一个函数被执行之后结果只有以上三种情况。那么我们来分贝测试一下:
let p = new Promise( (resolve, reject) => { resolve(); } ); let result = p.then( () => { throw "error"; } ); console.log(result);
首先我们让回调函数抛出异常看一下结果:
生成的Promise
对象是一个失败的Promise
,而且结果值就是我们的异常字符串。
let p = new Promise( (resolve, reject) => { resolve(); } ); let result = p.then( () => { return 123; } ); console.log(result);
这一次我们让回调函数返回一个非Promise
类型的数据看一下结果:
控制台中输出了一个成功的Promise
对象,而Promise
对象的结果就是我们的返回值。
let p = new Promise( (resolve, reject) => { resolve(); } ); let result = p.then( () => { return new Promise((resolve,reject)=>{ resolve("OK"); }); } ); console.log(result);
这一次呢我们让回调函数返回一个Promise
对象,这次返回的是一个成功的Promise
,那我们来看一下结果:
生成的Promise
对象也是一个成功的Promise
,而且结果也是我们回调函数返回的Promise
对象的结果值,那么如果我们的回调函数返回的是一个失败的Promise
对象呢?
let p = new Promise( (resolve, reject) => { resolve(); } ); let result = p.then( () => { return new Promise((resolve,reject)=>{ reject("error"); }); } ); console.log(result);
我这次再看一下结果:
这次then
方法生成的Promise
对象就是一个失败的Promise
,而且结果也是回调函数返回的Promise
对象的结果。
Promise
串联任务
这个词或许大家比较陌生,什么叫串联任务呢?我们之前不是说then
方法会返回一个新的Promise
对象嘛,那么这个新的Promise
对象是不是也可以调用一个then
方法?那么我们是不是直接链式调用像这样p.then(function).then(function)...
就可以了?这一步操作就是我们所谓的串联任务。
那么我们来演示一下:
let p = new Promise( (resolve, reject) => { resolve(123); } ); let result = p.then( () => { return new Promise((resolve, reject) => { resolve("OK"); }); }).then( value => console.log(value) );
我们来看上面这一段代码,我们首先实例化一个Promise
对象,这个对象里面可以执行同步任务也可以执行异步任务,然后再指定回调函数的时候,我们可以在回调中再开一个Promise
来执行一个新的同步或者异步任务。当then
方法调用完成之后,是不是获得了一个新的Promise
对象,那么这个对象是不是可以再调用then
方法?那么这个时候我们再在then
方法中指定回调函数,这样的话就实现了then
方法的链式调用,也就实现了串联任务,因为我们通过一次链式调用把两个任务给串联起来了。
我们来看一下,控制台中也成功输出了链式调用最后一节的回调的执行结果。
Promise
异常穿透
我们接下来来介绍另一个新的名词,叫异常穿透,那么什么叫做异常穿透呢?比如我们做了then
方法的链式调用,但是谁都没法保证在整个调用链的任何一个环节都不会报错。既然可能报错,那我们肯定得捕获异常,但是每个环节都写一个异常捕获机制的话也太麻烦了,这样的话Promise
的优势也没法体现出来,所以说我们可以用异常穿透来在整个调用链的最后一环来捕获异常:
let p = new Promise( (resolve, reject) => { setTimeout(() => { resolve("OK") }, 2000); } ); let result = p.then( value => { return new Promise((resolve, reject) => { console.log(value) setTimeout(() => { reject("error") }, 100) }); }).then( value => console.log(value) ).then( value => console.log(value) ).catch( reason => console.warn(reason) );
我们来看一下,我们在then
链式调用的最后一环调用了catch
方法,那么们来让整个调用链的其中一环失败来看一下结果:
我们看见我们是在第二环的异步任务失败了,然后打印出了失败的原因。那么我们如果是回调抛出异常了呢?
let p = new Promise( (resolve, reject) => { setTimeout(() => { resolve("OK") }, 2000); } ); let result = p.then( value => { return new Promise((resolve, reject) => { setTimeout(() => { resolve("OK!") }, 100) }); }).then( value => { throw "error"; } ).then( value => console.log(value) ).catch( reason => console.warn(reason) );
我们现在让调用链的第三环抛出异常再来看一下结果
这次我们看到控制台也成功不活了异常并执行了指定的回调。
Promise
链的中断
首先什么是Promise
链?我们如果之前用了then
方法的链式调用,就会形成一个调用的链状结构,这个链状结构就是我们所谓的Promise
链。我们来看一下我们现在的代码:
let p = new Promise( (resolve, reject) => { setTimeout(() => { resolve(111) }, 2000); } ); let result = p.then( value => { console.log(value) return new Promise((resolve, reject) => { setTimeout(() => { resolve(222) }, 100) }); }).then( value => console.log(value) ).then( value => console.log(value) ).catch( reason => console.warn(reason) );
现在我们这个Promise
链现在输出的情况是什么呢?
是不是除了catch
方法那一环以外,其他的都输出了,因为我们没有抛出异常也没有失败的Promise
,所以catch
方法并没有被调用。那么我们来看一下如果说我希望我们的Promise
链仅仅只执行完第一环就中断的话,应该怎么处理呢?我们说过then
方法被调用后会生成一个新的Promise
对象,而这个Promise
对象有几种状态?是不是三种?而then
方法是不是只能指定resolved
和rejected
的回调函数?那么我们如果返回一个在这两个状态之外的一个Promise
对象是不是在接下来的then
方法中什么回调都匹配不上啊?那么这样的话,是不是就可以直接中断我们的Promise
链了呢?我们来验证一下:
let p = new Promise( (resolve, reject) => { setTimeout(() => { resolve(111) }, 2000); } ); let p1 = new Promise(() => {}); console.log(p1); let result = p.then( value => { console.log(value) return new Promise(() => {}); }).then( value => console.log(value) ).then( value => console.log(value) ).catch( reason => console.warn(reason) );
我们这一次在第一环中返回了一个从来没见过的一个Promise
对象,我们是不知道这个Promise
对象到底是什么状态,所以我们单独声明这样一个Promise
对象来输出看一下:
首先我们看我们声明的这个Promise
对象,它的状态是pending
,那么在then
方法中将会一个回调都匹配不到,所以就仅仅只执行了Promise
链的第一环然后就中断了。
所以说中断一个Promise
链的方法就是让then
方法返回一个状态为pending
的Promise
对象。而且有且仅有这一种方法。
总结
以上便是Promise
中的几个关键问题。
- 修改
Promise
对象的状态有 3 种方法 resolve
函数reject
函数throw
关键字Promise
对象某状态如果指定了多个回调,那么当Promise
对象变更为该状态时,这些回调都讲被执行Promise
对象中如果封装的是同步任务,那么先改变状态,如果封装的是异步任务,则先指定回调then
方法返回的新Promise
对象分为三种情况- 如果回调中抛出异常,
then
方法将返回失败的Promise
对象,结果值为抛出的异常字符串 - 如果回调函数返回一个非
Promise
对象的数据,then
方法返回一个成功的Promise
对象,结果值为回调的返回值 - 如果回调函数返回一个
Promise
对象,then
方法返回一个状态和结果值都与回调函数返回的Promise
对象一致的新Promise
对象 then
方法的链式调用就是任务的串联,因为then
方法可以返回一个新的Promise
对象,而这个新的Promise
对象中可以开新的任务- 在
then
方法的调用链的最后一环添加catch
方法的调用可以捕获整条调用链中任何一环中的异常或者失败,这叫做异常穿透 - 可以在
then
方法的调用链中的任何一环中返回一个状态为panding
的Promise
对象来中断Promise
链
Copyright statement:The articles of this site are all original if there is no special explanation, indicate the source please when you reprint.
Link of this article:https://work.lynchow.com/article/import_question_promise/