抓重点理解Promise

前言

基本上面试问Promise首先会问你很简单的一句,“你了解过Promise吗”

这是时候只要你把我下面“是什么”的说明理解好了就足够了。但是假设面试官追问下去,你要对他的一些方法要有理解。

是什么

Promise是解决异步编程的一种方案。以前我们处理异步操作,一般都是通过回调函数来处理,典型的例子就好像使用setTimeout一样,如果执行操作函数里面还有setTimeout,一层一层往下,都有的话。那么代码看起来十分臃肿,不利于维护,也很容易写出bug。

而Promise的出现,能够让异步编程变得更加可观,把异步操作按照同步操作的流程表达出来,避免层层嵌套的回调函数。

Promise对象有三种状态,进行中pending、完成成功fulfilled、失败rejected,顾名思义,表示这个异步操作是进行中还是成功还是失败了。

Promise的状态一旦确定了,就不会再更改了,这就是promise(承诺)的由来吧,承诺状态确定了就是确定了。

然而Promise还是有不足的地方:

  1. 如果没有执行捕获错误的函数(如下述说的catch,then的第二个参数),则Promise内部发生的错误(虽然会报错但)是无法传递到Promise外部代码上的,因此外部脚本并不会因为错误而导致不继续执行下去。
  2. 一旦新建了,就无法中断它的操作。不像setTimeout那样,我还可以使用clearTimeout取消掉。

创建

promise是一种对象类型,即可通过new Promise()来创建一个promise实例对象。

let promise1 = new Promise((resolve, reject) => {
    if (xxx) {
        resolve('异步操作完成');
    } else {
        reject('异步操作失败');
    }
});

参数是一个函数,这个函数带有两个参数,两个参数都是函数类型,调用resolve方法,表示异步操作完成成功了;调用reject方法,表示异步操作失败了。

调用这两个方法,会返回一个新的Promise对象,而这个Promise对象的最终状态才是这个异步操作的最终状态。具体规则会在下面的resolvereject章节说明

在创建Promise对象时,参数的函数内的同步脚本会立即执行,如

let promise = new Promise(function(resolve, reject) {
  console.log('Promise');
  resolve();
});

promise.then(function() {
  console.log('resolved');
});

console.log('Hi!');

// Promise
// Hi!
// resolved

实际上上述的console.log('Promise');是同步脚本,resolve了之后的then方法是异步脚本。

要注意,执行了resolvereject方法后,并不会终止异步函数往下执行

let promise1 = new Promise((resolve, reject) => {
    if (xxx) {
        resolve('异步操作完成');
        console.log('keep on');
    } else {
        reject('异步操作失败');
    }
});

假设上述例子条件判断为true,则执行了resolve之后,还会执行console.log

一般执行resolvereject都是宣告异步操作这个函数本身完结了,其余还需要进一步的操作应该放在外部再处理(如下述说的在then里处理),所以我们一般是使用return宣告此部分动作完结

let promise1 = new Promise((resolve, reject) => {
    if (xxx) {
        return resolve('异步操作完成');
        console.log('keep on');
    } else {
        return reject('异步操作失败');
    }
});

这样就执行不到console.log了。

常用方法

then

关于then的执行顺序:then()本身是同步执行,只不过是.then的cb被放入了微任务队列,产生了异步执行。在ES6时代有了微异步的设定,then作为最典型代表,算是异步的一员。

关于promise和setTimeout的执行顺序:promise是微观任务,setTimeout是宏观任务,先执行微观任务,在执行宏观任务;微观任务里,先执行同步再执行异步 

经典面试题:

const promise = new Promise((resolve, reject) => {
  console.log(1);
  resolve(5);
  console.log(2);
}).then(val => {
  console.log(val);
});

promise.then(() => {
  console.log(3);
});

console.log(4);

setTimeout(function() {
  console.log(6);
});

执行结果: 124536 

then是promise的实例方法,接受两个参数,类型是函数。

promise.propotype.then(fn1, fn2)

fn1是表示该promise状态为成功时,需要进行的下一步操作,即被resolve了,fn1函数带有一个参数,这个参数就是promise里执行resolve(result)传的参数result。

fn2是可选的,表示该promise状态为失败时,需要进行的下一步操作,即被reject了,fn2函数带有一个参数,这个参数就是promise里执行reject(result)传的参数result,或者脚本抛出的错误信息。

例子

let promiseExample = new Promise((resolve, reject) => {
    if (xxx) {
        return resolve('异步操作完成');
    } else {
        return reject('异步操作失败');
    }
});

promiseExample.then((res) => {
    console.log(res);
}, (e) => {
    console.log(e);
});

上述例子如果xxx结果为true,则会输出“异步操作完成”,false的话输出“异步操作失败”。

then方法放回的是一个新的Promise实例,因此后面还可以接着then,因此可以采用链式写法

promise.then().then().then()

如果返回的还是一个Promise对象(即有异步操作),那么后面的then是要等前面的异步操作完成了才会执行。

如果你跟上面那几句话有点混乱的话,这么理解下:

then里的方法就算你不显式写return,then方法本身也是会返回一个Promise实例;但是如果你在里面的方法里显式return了一个promise对象,那么之后的then方法执行要等上一个返回的异步操作完成才会触发

catch

Promise的实例方法。catch方法实际上是then的一种特殊形式。用来捕获它前面的异步操作抛出的错误。即异步操作的失败时就可以调用该方法做进一步操作。

promise.then(null, fn2)
或
promise.then(undefined, fn2)

即无视了异步操作成功时的情况,执行失败时的情况。上述then中的例子可以改为以下形式:

let promiseExample = new Promise((resolve, reject) => {
    if (xxx) {
        return resolve('异步操作完成');
    } else {
        return reject('异步操作失败');
    }
});

promiseExample.then(res => {
    console.log(res);
}).catch(e => {
    console.log(e);
});

相当于

promiseExample.then(res => {
    console.log(res);
}).then(null, e => {
    console.log(e);
});

但是我们要注意,虽然catch方法和then里的第二个参数方法都是用来对异步操作失败时做进一步处理,但是他们还是有区别的。前者是能把链式结构中的“上游”部分中只要有抛出错误,就能捕获到,执行catch。而后者仅仅能捕获到它本次异步操作的失败。

promise.then().then(fn1, fn2).catch()

上述例子中,
catch能把前面的promise操作以及前两个then操作中的错误给捕获到。而例子中的fn2函数,只能捕获到第一个then里执行错误的情况

因此我们建议,使用catch替换用then的第二个参数来捕获错误,而且书写格式也比较直观。

吃掉错误

如果不使用catch捕获Promise操作抛出的错误,那么就算报错了也不会影响到外部代码,不会退出进程、终止脚本。

const asyncDoing = function() {
  return new Promise(function(resolve, reject) {
    // 下面一行会报错,因为i没有声明
    resolve(i === 5);
  });
};

asyncDoing().then(function() {
  console.log('have Done');
});

setTimeout(() => { console.log(111) }, 5000);

// 控制台最终输出
// Uncaught (in promise) ReferenceError: i is not defined
// 111

先是浏览器抛出i未定义的错误,后面就打印出111

我们之前的认识是,在同步脚本里,如果某个脚本出错了,会终止下面的脚本继续执行,但是这里并不会

catch方法也是返回一个Promise对象,如果后面接着一个then方法,那么catch前的Promise对象如果没有抛出错误,那么会跳过catch,然后执行then,如果有错,那么先执行catch再执行then

finally

Promise的实例方法。上面我们说到用then来处理异步成功,用catch处理失败,那么如果我们并不在乎他们是否成功失败,都要在之后执行某个操作,就可以是用final方法了。用法跟上面的很相似。

all

类方法。当你有好几个异步操作,你想等他们都执行完了之后再执行某个操作,那么就要用到all方法了。

Promise.all([promise1, promise2, promise3]).then(res).catch(e);

参数数组里面的每个元素都是Promise对象,如果原本不是的,会使用下面说到的Promise.resolve进行转化。参数是的形式不局限于数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。

返回的状态的情况:

  1. 如果全部promise都是成功的,那么就返回成功状态,执行then里的函数,res是一个数组,里面的元素按照all参数里的promise的顺序一一对应它们各自的resolve
  2. 如果有一个promise处理失败了,那么整个状态就是失败的。执行catch,e为对应的错误信息。
  3. 如果参数里的Promise实例本身有使用catch捕获错误,那么对于all来讲,是返回了fullfilled状态的

race

类方法。如有一批异步操作,你只想当它们之中有一个事先结束了就执行某个操作,就可以使用它。

Promise.trace([promise1, promise2, promise3])

参数跟all方法一样。

如果promise2是第一个异步执行完的,那么就以它的状态为最终状态。

resolve

类方法。执行该方法返回一个Promise对象

Promise.resolve(param);

等价于

new Promise(resolve => resolve(param))

关于返回的Promise的状态按照如下规则:

  1. param如果是一个promise对象,则原封不动地原样返回这个promise对象
  2. 如果还是一个promise对象,但是对它已经有了如thencatch的操作,上面我们也说过这两个方法还是可能会返回Promise对象,所以会以这个最终状态的Promise对象返回。其实也就是上一个规则的特性情况而已
  3. 如果除上述两个情况外的其他值(包括不传),那么会返回成功状态的Promise对象

以上规则同样是使用在Promise对象中的resolve方法

reject

类方法。执行该方法返回一个Promise对象

Promise.reject(param);

关于返回的Promise的状态规则很简单,与上述的resolve是不同的

  1. param如果是Promise对象,那么原样返回,尽管它可能本身还有thencatch操作,也不管。这就是不同之处
  2. 如果除上述两个情况外的其他值(包括不传),那么会返回失败状态的Promise对象

以上规则同样是使用在Promise对象中的reject方法

发布了68 篇原创文章 · 获赞 32 · 访问量 14万+

猜你喜欢

转载自blog.csdn.net/Web_J/article/details/102460535