Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

抓重点理解Promise #11

Open
pekonchan opened this issue Aug 13, 2019 · 2 comments
Open

抓重点理解Promise #11

pekonchan opened this issue Aug 13, 2019 · 2 comments

Comments

@pekonchan
Copy link
Owner

pekonchan commented Aug 13, 2019

前言

基本上面试问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是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方法

@rxdxxxx
Copy link

rxdxxxx commented Aug 21, 2019

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

如果没有执行捕获错误的函数(如下述说的catch,then的第二个参数),则异步操作里发生的错误是无法反馈到外部的,俗话说“吃掉了错误”,因此外部脚本并不会因为错误而导致不继续执行下去

不写catch同样会被外界捕获啊, 你说的这种情况能举个例子吗?

@pekonchan
Copy link
Owner Author

pekonchan commented Aug 22, 2019

@rxdxxxx

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

如果没有执行捕获错误的函数(如下述说的catch,then的第二个参数),则异步操作里发生的错误是无法反馈到外部的,俗话说“吃掉了错误”,因此外部脚本并不会因为错误而导致不继续执行下去

不写catch同样会被外界捕获啊, 你说的这种情况能举个例子吗?

我这边措辞严谨点表达,如果没有执行捕获错误的函数(如下述说的catch,then的第二个参数),则异步操作里发生的错误虽然会报错出来,但是错误不会传递到Promise外部代码,外部脚本并不会因为错误而导致退出进程、终止脚本执行(正常情况下你脚本报错了,下面的脚本并不会继续执行下去)

执行下面的例子可以看到

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

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

setTimeout(() => { console.log('运行了timeout') }, 2000);
// Uncaught (in promise) ReferenceError: x is not defined
// 运行了timeout

尽管报错了x is not defined,但还是会执行了定时器里的脚本

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants