Promise常用学习

225 阅读15分钟

前言

只记录常用的方法,更加详细去阮一峰老师的网站查看Promise

有关Promise想要刷题的建议这里。超级厉害!!!!

一、基础知识

1.Promise对象特点

熟记这些特点

自身特点
  • 对象状态不受外界影响 对象的状态总共有三种(pending、fulfilled、rejected),只有异步操作结果能决定是哪一种状态,其他手段无法改变

  • 状态改变,就定型 状态改变只有两种情况(pending——>fulfilled,pending——>rejected),一旦状态改变,状态就凝固,并且一直保持这个结果。

  • 无法取消 一旦新建就会立即执行,状态为pending,中途无法取消

与Event Loop的联系
  • new Promise立即执行 Promise构造函数中的代码是同步执行的。并且resolve/reject是不会终结Promise的参数函数执行的
new Promise((resolve, reject) => {
  resolve(1);
  console.log(2);
}).then(r => {
  console.log(r);
});
//2
//1
  • 回调函数是微任务Promise状态不为pending并且调用Promise.then(),这时会把对应的回调函数添加到微任务队列中

只有Promise的状态不为pending时,才会把对应then、catch、finnaly回调函数加入微任务队列中,所以需要注意P调用这些方法的promise状态变化时机,容易和微任务和宏任务队列相关结合

2.基本用法

new Promise
const promise = new Promise(function(resolve, reject) {

  if (/* 异步操作成功 */){
    resolve(value);
  } else {
    reject(error);
  }
});

  1. Promise是一个构造函数,接收一个函数为参数,函数参数为resolve和reject

  2. resolve和reject的作用就是将pending状态改变为resolved/rejected

  3. Promise生成的实例保存着异步操作的结果(Promsise对象的状态),比如像以下这样

Promise { <pending> } 、Promise { 1 }、Promise { <rejected> 1 }

Promise.prototype.then()

then方法是定义在构造函数原型上的方法,作用: 为Promise实例添加状态改变时的回调函数。

promise.then(function(value) {
  // success
}, function(error) {
  // failure
});
  1. then方法接收俩个回调函数作为参数
  2. promise实例保存的结果(Promise对象的状态)决定触发哪一个回调函数
  3. 如果promise对象状态为pending的话,就不会触发上述的回调函数
  4. then方法返回的是一个新的Promise实例--详细用法在进阶用法中
Promise.prototype.catch()

catch()方法是.then(null,rejection)then(undefined,rejection)的别名,用来指定发生错误时的回调函数.

const promise = new Promise(function(resolve, reject) {
  throw new Error('test');
});

promise.then(res=>{
},rej=>{
})
.catch( function(error) {
  // failure
});
  1. 参数是一个回调函数
  2. 当Promise对象状态变为rejected,就会调用catch()方法指定的回调函数
  3. 当Promise对象抛出错误时,也会被catch捕获
  4. then()方法指定的回调函数,如果运行中抛出错误,也是由catch()方法捕获
  5. catch()方法返回的是一个Promise实例

小结: 关于catch和then方法,后续会讲链式调用的问题。还得记住他们的一个特点-- 返回的值不能是 promise 本身,否则会造成死循环。

const promise = Promise.resolve().then(() => {
  return promise;
})
promise.catch(console.err)
Promise.prototype.finally()

promise结束时,无论结果是fulfilled或者是rejected,都会执行指定的回调函数

  • .finally()方法的回调函数不接受任何的参数,也就是说你在.finally()函数中是没法知道Promise最终的状态是resolved还是rejected

  • finally回调函数内部最终返回的默认会是一个上一次的Promise对象值,不过如果抛出的是一个异常则返回异常的Promise对象。

Promise.resolve('2')
  .finally(() => {
    console.log('finally2')
  	return '我是finally2返回的值'
  })
  .then(res => {
    console.log('finally2后面的then函数', res)
  })
  
//打印 finnaly2-->finally2后面的then函数,2

抛出错误

Promise.resolve('1')
  .finally(() => {
    console.log('finally1')
    throw new Error('我是finally中抛出的异常')
  })
  .then(res => {
    console.log('finally后面的then函数', res)
  })
  .catch(err => {
    console.log('捕获错误', err)
  })
  
'finally1'
'捕获错误' Error: 我是finally中抛出的异常

Promise.resolve()

作用: 转换成为一个新的Promise对象。

Promise.resolve(value)等价于new Promise(resolve => resolve(value))

参数主要有四种情况:

  1. 参数是一个Promise实例 不做任何改变,原封不动的返回这个实例

  2. 参数是thenable对象 也就是说具有then方法的对象,

let thenable = {
  then: function(resolve, reject) {
    resolve(42);
  }
};

let p1 = Promise.resolve(thenable);
p1.then(function (value) {
  console.log(value);  // 42
});
  1. 参数是和Promise没关系的值 可以是原始值,也可以是普通对象,这时候会返回一个状态为resolved的Promise对象
const p = Promise.resolve('Hello');

p.then(function (s) {
  console.log(s)
});
  1. 不带任何参数 返回一个resolved状态的 Promise 对象。
Promise.reject()

作用: 转换成为一个新的Promise对象。该实例的状态为rejected。

Promise.reject()方法的参数,会原封不动地作为reject的理由,变成后续方法的参数。

Promise.reject('出错了')
.catch(e => {
  console.log(e)//出错了
})

Promise.all()

这个方法用于多个Promise实例,返回是是一个新的Promise实例。

Promise.all就是多个异步任务并发运行,它的结果在创建承诺之后使用,等待所有任务结果完成。

  • 参数:
  1. 接收一个数组作为参数,参数需要都是promise实例,如果不是会调用Promise.resolve()将传入的参数转化为Promise实例。
Promise.all([
1,
'1'
//相当于Promise.resolve(1)
])
.then((res)=>{
  console.log(res);//  [1,'1']
})
  1. 参数也可以不是一个数组 但是得有Iterator接口,且每个成员都是Promise实例
  • 返回值

const p = Promise.all([p1, p2, p3]); p的状态是由p1、p2、p3决定的

  1. 返回有状态的Promise

只有当数组中promise实例状态都为resolved时,或者其中有一个变为rejected,调用then/catch方法才会去触发回调函数

var p1 = new Promise((res,rej)=>{
//无状态
})
var p2 = new Promise((res,rej)=>{
  res("p2 res")
})

var all = Promise.all([
  p1,p2
])

console.log(all)//Promise { <pending> }

all.then((res)=>{
  console.log("success---"+res);
})
.catch((err)=>{
  console.log("fail---"+err);
})
  1. 返回resolved状态的数组

当数组中返回的都是resolved状态的Promise,则返回一个触发所有回调函数的结果值的数组

Promise.all([
1,
'1'
//相当于Promise.resolve(1)
])
.then((res)=>{
  console.log(res);//  [1,'1']
})
  1. 返回rejected状态的Promise

当数组中返回的Promise实例只要有一个为rejeced,整体的返回结果就是触发的第一个失败状态的回调函数

var p1 = new Promise((res,rej)=>{
  res("p1 res")
})
var p2 = new Promise((res,rej)=>{
  rej("p2 rej")
})
var p3 = 1;
var p4 = new Promise((res,rej)=>{
  rej("p4 rej")
})
Promise.all([
  p1,p2,p3,p4
])
.then((res)=>{
  console.log("success---"+res);
})
.catch((err)=>{
  console.log("fail---"+err);//fail---p2 rej
})

注意: 即使Promise.all参数数组中第一个Promise实例已经是rejected,整个Promise.all的状态将为rejected。但是其参数数组rejected状态后面的promise还是会执行的

image.png

  • rejected阻塞所有 当Promise.all中一个rejected导致返回的Promise状态为rejected,就不能接收到别的参数中promise实例的状态

可以使用catch去接收不论什么状态的promise实例,从而最终都能到Promise.all的then回调之中

var p1 = new Promise((resolve, reject) => {
	resolve('p1');
});
var p2 = new Promise((resolve, reject) => {
	resolve('p2');
});
var p3 = new Promise((resolve, reject) => {
	reject('p3');
});
Promise.all([p1, p2, p3].map(p => p.catch(e => '出错后返回的值' )))
  .then(values => {
    console.log(values);
  }).catch(err => {
    console.log(err);
  })

3.进阶用法

当Promise实例作为resolve参数
const p1 = new Promise(function (resolve, reject) {
  reject("p1 rejected")
});

const p2 = new Promise(function (resolve, reject) {
  resolve(p1);
})
p2.then((res)=>{
  console.log("p2 resolved")
},(rej)=>{
  console.log(rej);//p1 rejected
})

上述代码,打印为p1 rejected

  1. 很显然,一个异步操作的结果p2是返回另一个异步操作p1
  2. p1的状态会传递给p2,因此p2的状态是由p1的返回结果决定的,而不是由p2的Promise对象resolve决定的。
链式调用(重要)

提起链式调用,不知道你们想起Jquery没有,它就是有链式调用的,但是它的链式调用依靠的是在Jquery函数内部 return一个this,从而实现的。而then方法是通过return一个新的promise

由上面的基本用法,我们可以知道catch()、then()方法的返回结果是一个新的Promise实例,因此可以接着调用catch(),then()方法。

关于链式调用我们明确以下几点:

  • new Promise对象状态为pending时,是不会触发then方法中的回调函数的
const p1 = new Promise(function (resolve, reject) {

});

p1.then((res)=>{
  console.log(" resolved")
},(rej)=>{
  console.log("rejected");
})
  • then方法会根据上一个then方法或者catch方法返回的Promise对象状态,决定then方法触发哪个回调函数。具体区分为以下两种情况:
  1. 当前then方法触发resolved回调函数

第一,上一个then无返回值或者return一个值,这时候默认返回Promise.resolve(undefined)或者Promise.resolve(value)

注意:这里的return值,是不管上一个then触发的是哪一个回调函数,即使上一个then触发的是失败的回调函数,只要它return undefined/普通值,此时的then都是触发成功的回调函数

注意:return new Error()也是返回一个resolved状态的promise,和后面的throw new Error()是不一样的!!!!

const p1 = new Promise(function (resolve, reject) {
  resolve("p1 resolve")
});

p1.then((res)=>{
  console.log(res)
//  Promise.resolve(undefined)
},(rej)=>{
  console.log("rejected");
})
.then((res)=>{
  console.log(res)//打印undefined
},rej=>{

});

以上代码,在第一次then方法中会返回一个resolved状态的Promise对象,并且值为undefined

第二,上一个thenreturn一个新的promise成功态结果(resolve)

const p1 = new Promise(function (resolve, reject) {
  resolve("p1 resolve")
});

p1.then((res)=>{
  console.log(res)
 return  new Promise((res,rej)=>{
  res("first then resolve");
 })
},(rej)=>{
  console.log("rejected");
})
.then((res)=>{
  console.log(res)//打印"first then resolve"
},rej=>{

});
  1. 当前then方法触发rejected回调函数

第一,上一个then return了一个新的promise失败态的结果(reject)

const p1 = new Promise(function (resolve, reject) {
  resolve("p1 resolve")
});

p1.then((res)=>{
  console.log(res)
  return  new Promise((res,rej)=>{
    rej("first then reject");
   })
},(rej)=>{
  console.log("rejected");
})
.then((res)=>{
  console.log(res)
},rej=>{
  console.log(rej)//first then reject
});

第二,上一个then throw new error 抛出了异常

const p1 = new Promise(function (resolve, reject) {
  resolve("p1 resolve")
});

p1.then((res)=>{
  console.log(res)
  throw new Error("抛出 reject")
},(rej)=>{
  console.log("rejected");
})
.then((res)=>{
  console.log(res)
},rej=>{
  console.log(rej)//Error: 抛出 reject
});
  • 联系Promise.all 看看这个题 和Promise.all结合起来做做看
const p1 = new Promise((resolve, reject) => {
  resolve('hello');
})
.then(result => result)
.catch(e => e);

const p2 = new Promise((resolve, reject) => {
  throw new Error('报错了');
})
.then(result => result)
.catch(e => e);

Promise.all([p1, p2])
.then(result => console.log(result))
.catch(e => console.log(e));
// ["hello", Error: 报错了]

p1会resolved,p2首先会rejected,但是p2有自己的catch方法,该方法返回的是一个新的 Promise 实例,p2指向的实际上是这个实例。该实例执行完catch方法后,也会变成resolved,导致Promise.all()方法参数里面的两个实例都会resolved,因此会调用then方法指定的回调函数,而不会调用catch方法指定的回调函数。

Promise对象的错误有‘冒泡’性质

这个性质其实也是基于链式调用才能实现的。

一直向后传递,直到被捕获为止。也就是说,错误总是会被下一个catch语句捕获或者then方法rejected回调函数捕获。

const p1 = new Promise(function (resolve, reject) {
  resolve("p1 resolve")
});

p1.then((res)=>{
  console.log(res)//p1 resolve
  throw new Error("抛出 reject")
},(rej)=>{
  console.log("rejected");
})
.then((res)=>{},rej=>{
  console.log(rej+"---then捕获")//Error: 抛出 reject---then捕获
})
.catch(err=>{
  console.log(err+"---catch捕获")
})
then、catch参数问题

.then 或者 .catch 的参数期望是函数,传入非函数则会发生值透传。

  
//输入结果为5
Promise.resolve(1)
  .then(2)
  .then(Promise.resolve(3))
  .then(console.log)


//输出结果为5
Promise.resolve(1)
  .then(res=>{
      return 5
  })
  .then(Promise.resolve(3))
  .then(console.log)

在Event Loop中的运用
  • promise.then是一个微任务,所以要注意它的执行时间

在promise.then还没执行之前,它返回的一直是一个pending状态的Promise

const promise1 = new Promise((resolve, reject) => {
  console.log('promise1')
  resolve('resolve1')
})
const promise2 = promise1.then(res => {
  console.log(res)
});

console.log('2', promise2);

上述打印顺序为

image.png

具体原因为:

  1. 先遇到new Promise,执行该构造函数中的代码promise1 碰到resolve函数, 将promise1的状态改变为resolved, 并将结果保存下来

  2. 碰到promise1.then这个微任务,并且状态不为pending,将对应的回调函数放入微任务队列

  3. promise2是一个新的状态为pending的Promise

  4. 执行同步代码‘2’,同时打印出promise2的状态是pending

  5. 宏任务执行完毕,查找微任务队列,发现promise1.then这个微任务并且执行它,状态更改为resolved

结合setTimeout使用
  • setTimeout 它是一个宏任务,但是不会被放在现有宏任务队列,而是下一个宏任务队列
const promise = new Promise((resolve, reject) => {
  console.log(1);
  setTimeout(() => {
    console.log("timerStart");
    resolve("success");
    console.log("timerEnd");
  }, 0);
  console.log(2);
});
promise.then((res) => {
  console.log(res);
});
console.log(4);

以上代码输出

image.png

可以自己结合着之前总结的那些,分析分析。有关宏任务和微任务只有具体问题具体分析。

二、实际应用

1. 实现Ajax操作

Promise实际上是充当ajax获取后台数据后执行回调函数的异步执行顺序的一个媒介。

const getJson = function(url){
  const promise = new Promise((resolve,reject){
    const handler = function () {  
      if(this.readyState!==4){
        return;
      }
      if(this.status ===200){
        resolve(this.responseText);
      }else{
        reject(new Error(this.statusText));
      }
    }
    const xhr = new XMLHttpRequest();
    xhr.open("GET",url);
    xhr.onreadystatechange = handler;
    xhr.setRequestHeader("Accept","application/json");
    xhr.send();
  })
  return promise;
}
getJson("http://localhost:8000").then((json)=>{
  console.log(json);
},(error)=>{
  console.log(error);
})

2.异步加载图片

function getImg(url) {  
  return new Promise((resolve,reject){
    let img  = new Image();
    img.onload = function () {  
      console.log("img 加载完成");
      resolve(img)
    }
    img.onerror = function(){
      reject("fail")
    }
    img.src = url;

  })
}

三、重写Promise(重要)

主要是then、catch方法

1.实现最基本Promise构造函数

首先清楚我们要实现的点子:

  • new Promise时其参数(executor)自动执行
  • promise状态首先为pending,之后通过executor函数中参数resolve/reject改变状态
  • then方法根据Promise状态触发对应的回调函数
  • Promise能够throw new Error 并且then方法第二个参数能够捕获

抛出错误是在executor执行之后,因此利用try catch来捕获错误


const PENDING = 'PENDING',
      FULFILLED = 'FULFILLeD',
      REJECTED = 'REJECTED';

class MyPromise {
  constructor(executor) {
    // 声明状态
    this.status = PENDING
    //对应resolve和reject的值
    this.value = undefined
    this.reason = undefined


    //  resolve参数
    const resolve = (value) => {
   
      if (this.status === PENDING) {
        this.status = FULFILLED
        this.value = value
      }
    }
    //  reject参数
    const reject = (reason) => {
         //状态改变
      if (this.status === PENDING) {
        this.status = REJECTED
        this.reason = reason

      }
    }
    //捕获错误
    try{
    //执行构造函数
      executor(resolve);
    }catch(e){
      reject(e);
    }
  }
  then(onFulfilled,onRejected){
    if(this.status===FULFILLED){
      onFulfilled(this.value);
    }

    if(this.status===REJECTED){
      onFulfilled(this.reason);
    }
  }
}

module.exports = MyPromise;

以上我们就实现了基本的Promise,接下来是查找问题了。

2.处理pending状态(异步处理)

Promise本身就是用来处理异步程序的对吧,那我添加上异步程序,发现没有变化

image.png

主要原因就是实现MyPromise中因为setTimeout函数异步的关系,函数中的状态还是pending,所以之后的promise1.then不能处理。

因此我们在promise.then时收集回调函数,在executor参数状态改变时去执行,这只是针对pending状态的promise。不会影响到其他状态。

需要实现的功能: 处理pending状态 使用了发布-订阅的模式

下面代码只放修改了的部分:


  constructor(executor) {
    //收集成功、失败的回调函数
    this.onResolveCallbacks = [];
    this.onRejectedCallbacks = [];

    //  resolve参数
    const resolve = (value) => {
      if (this.status === PENDING) {
        //发布 异步任务到时间执行
        this.onResolveCallbacks.forEach((fn)=>fn());
      }
    }
    //  reject参数 
    const reject = (reason) => {
      if (this.status === PENDING) {
      //发布 异步任务到时间执行
        this.onRejectedCallbacks.forEach((fn)=>fn());
      }
    }
    
  }
  //then方法
  then(onFulfilled,onRejected){
    //处理pending状态 订阅的过程
    if(this.status===PENDING){
      //收集成功的回调
      this.onResolveCallbacks.push(()=>{
        onFulfilled(this.value)
      })
      //收集失败的回调
      this.onRejectedCallbacks.push(()=>{
        onRejected(this.reason);
      })
    }
  }
}

3.实现链式调用(重要)

很明显之前实现的myPromise是不能进行链式掉用的,所以接下来是实现链式调用。

首先得清楚Promise链式调用基本特点。可以查看之前归纳的特点。

实现功能:

  • then方法会返回一个promise对象
 then(onFulfilled,onRejected){
     let promise2 = new Promise((resolve,reject)=>{
         //之前的coding
     })
     return promise2
  }
  • then方法两个回调函数是异步的 因为之后我们需要对x进行判断,需要取到Promise2这个对象,前提是这个回调函数得执行,因此是需要对回调函数进行异步处理的

利用setTimeOut对回调函数进行异步处理

image.png

  • then方法触发回调函数执行后会返回一个结果由x接收。 对于这个返回结果x需要进行判断处理。resolvePromise函数处理这个返回结果

对于resolvePromise函数参数主要有四个

image.png

接下来是这个函数的逻辑:

  1. 回调函数return的值不能是返回的promise对象

image.png

  1. 判断x是不是一个promise对象,不是一个promise对象时直接resolve()就好。

image.png

  1. 获取x.then时可能会throw new Error 在获取x.then这个值时,可能会被拦截,从而抛出异常,因此也需要去处理

image.png

  1. 当判断x是一个promsie对象时,将then方法的this指向改变,并传递回调函数

image.png

  1. 如果x是一个Promise对象,只有第一个resolve/reject才会生效

image.png

  1. 当新返回的x再次resolve新的promise对象,会形成嵌套

image.png

  1. 当promise.then()不传入参数,会导致后面的onFulfilled...无法使用,因此需要传递默认值

image.png

  • 返回结果x是throw new Error需要去捕获异常 像下面的代码一样,在回调函数执行结束之后,去捕获异常

image.png

4.实现catch方法

因为catch方法基本和then方法一致,只是第一个参数不同,只需要返回当前实例对象then方法,第一个参数赋值为空就好

image.png

5.总结代码

可以去试一下,应该是没问题的

const PENDING = 'PENDING',
  FULFILLED = 'FULFILLED',
  REJECTED = 'REJECTED';

function resolvePromise(promise2, x, resolve, reject) {
  if (promise2 === x) {
    return reject(new TypeError('Chaining cycle detected for promise #<MyPromise>'))
  }

  let called = false;

  if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
    try {
      let then = x.then; // throw error

      if (typeof then === 'function') { // Promise
        then.call(x, (y) => {
          if (called) return;
          called = true;
          resolvePromise(promise2, y, resolve, reject);
        }, (r) => {
          if (called) return;
          called = true;
          reject(r);
        })
      } else {
        resolve(x);
      }
    } catch (e) {
      if (called) return;
      called = true;
      reject(e);
    }
  } else {
    resolve(x);
  }
}

class MyPromise {
  constructor(executor) {
    this.status = PENDING;
    this.value = undefined;
    this.reason = undefined;

    this.onResolveCallbacks = [];
    this.onRejectedCallbacks = [];

    const resolve = (value) => {
      
      if (value instanceof MyPromise) {
        value.then(resolve, reject);
        return;
      }

      if (this.status === PENDING) {
        this.status = FULFILLED;
        this.value = value;
        // 发布
        this.onResolveCallbacks.forEach((fn) => fn());
      }
    }

    const reject = (reason) => {
      if (this.status === PENDING) {
        this.status = REJECTED;
        this.reason = reason;
        // 发布
        this.onRejectedCallbacks.forEach((fn) => fn());
      }
    }

    try {
      executor(resolve, reject);
    } catch (error) {
      reject(error);
    }
  }

  // x 普通值  promise
  then(onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
    onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason };

    let promise2 = new MyPromise((resolve, reject) => {
      if (this.status === FULFILLED) {
        setTimeout(() => {
          try {
            let x = onFulfilled(this.value);
            resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            reject(e);
          }
        }, 0);
      }

      if (this.status === REJECTED) {
        setTimeout(() => {
          try {
            let x = onRejected(this.reason);
            resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            reject(e);
          }
        }, 0);
      }

      if (this.status === PENDING) {
        // 订阅
        this.onResolveCallbacks.push(() => {
          setTimeout(() => {
            try {
              let x = onFulfilled(this.value);
              resolvePromise(promise2, x, resolve, reject);
            } catch (e) {
              reject(e);
            }
          }, 0);
        });
        this.onRejectedCallbacks.push(() => {
          setTimeout(() => {
            try {
              let x = onRejected(this.reason);
              resolvePromise(promise2, x, resolve, reject);
            } catch (e) {
              reject(e);
            }
          }, 0);
        });
      }
    });

    return promise2;
  }

  catch (errorCallback) {
    return this.then(null, errorCallback);
  }
}

module.exports = MyPromise;

四、Promise.all重写

Promise.all = function(promiseArr) {
    let index = 0, result = []
    return new Promise((resolve, reject) => {
        promiseArr.forEach((p, i) => {
            Promise.resolve(p).then(val => {
                index++
                result[i] = val
                if (index === promiseArr.length) {
                    resolve(result)
                }
            }, err => {
                reject(err)
            })
        })
    })
}