Promise再来过一遍呗

716 阅读6分钟

[TOC]

阮老师的promise是不是很难看完,看这个吧,我给精简总结下

1. 什么是Promise

简单说是一个容器,里面保存着未来才会结束的事件(通常是异步操作)的结果

  • 特点

    • 状态不受外界影响,(pending进行中, fulfilled已成功 ,rejected已失败 )。只有异步操作的结果决定当前是哪一状态。下文用 resolved表示fulfilled
    • 状态一旦改变,就不会再改变。从pending变为fulfilled和从pending变为rejected,这时称resolved 已定型

    通过Promise,可将异步操作变为同步操作的流程表达出来,避免回调嵌套地狱,使得异步操作更加容易。

    但是:

      1. Promise一新建就会立即执行,无法中途取消
      1. 当处于pending状态时,无法得知当前是刚开始还是即将完成。
      1. 内部抛出的错误,如果不设置回调函数,不会反映到外部。

2. 基本使用

1. new Promise()

构造方法接收一个函数,该函数由js引擎提供的两个函数参数resolvereject

  • resolve: 成功,状态pending => resolved,并将异步操作结果作为参数传递出去。
  • reject:失败, 状态 pending => rejected, 并将异步操作报出的错误,作为参数传递出去。
const promise = new Promise(function(resolve, reject) {
  //...some code
  if (/* 异步操作成功 */) {
      resolve(value);
  } else {
    reject(error);
  }
});

2. Promise.resolve()

将现有对象转为Promise对象。

const ajaxPromise = Promise.resolve($.ajax('./whatever.json'));

等价写法:

Promise.resolve('foo');
// <=>
new Promise(resolve => resolve('foo'))

其参数情况:

  • 参数是Promise实例: 什么都不做,直接返回。

  • 参数是 thenable对象(指具有 then方法的对象):将这个对象转为Promise对象,然后立即执行 thenable对象的 then方法

  • 参数不是具有 then方法的对象, 或根本就不是对象:返回一个状态为resolved新的Promise对象,该参数会同时传给回调函数

    const  p = Promise.resolve("mouse");
    p.then(str => {console.log(str)});// "mouse"
    
  • 不传: 直接返回一个resolved状态的Promise对象。

注: resolve() 的Promise对象,是在本轮 event loop结束时执行

setTimeout(()=>{console.log(1)}, 0);//下一轮 event loop开始时执行。
Promise.resolve().then(()=>{console.log(2)});
console.log(3);
//3
//2
//1

3. Promise.reject()

也会返回一个新的Promise实例,但其状态rejected

参数说明:

Promise.resolve()不同,它的参数会不做修改的作为reject的理由,变成后续方法的参数

const thenalble = {
  then(resolve, reject) {reject('some error')}
};
Promise.reject(thenable).catch(e=>{
  console.log(e === thenable);// true
  
});

4. Promise.prototype.then()

实例方法, Promise状态改变时的回调函数。有两个参数,对应改变的两个状态

  1. resolved状态的回调函数。
  2. rejected状态的回调函数。

then方法返回一个新的Promise实例,因此可以可以链式调用

5. Promise.prototype.catch()

用于指定发生错误时候的回调函数,其实就是 rejected时的回调,所以等同于:

.then(null|undefinded, rejection)

一搬写在最后,捕获前面链式调用出现的错误

注:

  1. 状态如果已经变成 resolved, 再抛出错误是无效的,因为状态一改变,就永久改变了。所有才叫 ”Promise“。

  2. Promise对象的错误具有 冒泡 性质,会一直向后传递,直到被捕获为止。

  3. 一搬来说,不在then里面使用第二个参数,而总是使用catch。使用catch更接近同步的写法,且可catch前面所有执行中的错误。

  4. 如果没有使用catch,Promise对象抛出的错误不会传递到外层代码,外层代码依旧正常执行。”Promise会吃掉错误“。

  5. 其返回值还是一个Promise对象,所有还是可以继续then

6. Promise.prototype.finally()

看到这个总能想起,java里面:不管怎么样,数据库用完了总是要关闭的。

finally:不管怎么样,最后都要执行的操作。与状态无关。本质上就是then的特例

promise.finally(()=>{
  // some code 
}); 
//等同于
promise.then((result)=>{
  // some code 
  return result;
}, (error)=>{
  // some code 
  throw error;
});

finally()方法帮忙给简写了。

finally()方法实现:

Promoise.prototype.finally = function(callbakc) {
  let p = this.constructor;
  return this.then(
  	value => P.resolve(callback()).then(()=>value),
     reason => P.resolve(callback()).then(()=>{throw reason})
  );
}
//finally.总是会返回原来的值。

7. Promise.all()

将多个Promise实例,包装成一个新的Promise实例

const  p = Promise.all([p1, p2, p3]);
//参数是Promise实例的数组,如果数组成员不是promise,
//会先调用Promise.resolve()。参数也可以不是数组,但必须具有Iterator接口。

p的状态由p1, p2,p3决定:

  • p1, p2, p3的状态都变成resolved,则p的状态变成resolved。此时 p1, p2, p3的返回值组成一个数组,传递给p的回调函数。
  • p1, p2, p3其中一个被rejected,则p的状态为rejected。此时第一个reject的实例的返回值,会传递给p的回调函数。

8. Promise.race()

同样是将多个Promise实例,包装成一个新的Promise实例。

const p = Promise.race([p1, p2, p3]);
//参数同 Promise.all();

只要 p1, p2, p3,之中有一个实例率先改变状态。p的状态就跟着改变。率先改变的Promise实例的返回值,就传递给p的回调函数。

一个典型的应用就设置请求超时

const p = Promise.race([
  fetch('/remoteData'),
  new Promise((resolve, reject)=>{
    setTimeout(()=>{reject(new Error('request timeout')},5000);
  })
]);
p.then(console.log).catch(console.error);

9. Promise.allSettled() 由ES2020引入

同样是多个Promise实例,包装成一个新的Promise实例。

只有等所有参数实例都返回结果,包装实例才会结束。

Promise.all(),无法确定所有请求都结束。

10. Promise.any() 目前还在 提案

同样多个Promise实例,包装成一个Promise实例。 只要参数实例中,有一个resolved了,包装实例状态就会变成resolved

Promse.race()相像。不过Promise.any()不会因为某个Promise变成rejected状态而结束。

11. Promise.try()

想用Promise,又不用区分是同步还是异步函数,比如: 不管函数f 是否包含异步操作,都用 then方法指定下一步流程。

一般做法:

Promise.resolve().then(f);
//此时如果f是同步函数,则其会在本轮event loop的末尾执行。eg:
const f = () => console.log(1);
Promise.resolve().then(f);
console.log(2);
//2
//1

try的作用就是让同步函数同步执行,异步函数异步执行。

const f = () => console.log(1)
Promise.try(f);
console.log(2);
//1
//2

​ 注: Promise的库:Bluebird , Q, when都提供的这个方法。

鉴于目前还是提案,使用的话可以用shim: ​ npm i es6-promise-tryimport promiseTry from "es6-promise-try"

阮老师给出的另外两种方法:

//第一种
const f = () => {console.log(1)};
(async () => f())().then().catch();//async()=> f()会吃掉 f()抛出的异常,需要Promise.catch()
console.log(2);
//第二种
const f = ()=>console.log(1);
(()=>{new Promise(resolve=>resolve(f()))})();
console.log(2);