什么是Promise

Promise 是一个构造函数,是用来解决JS异步编程(回调地狱)的一种解决方案,怕有些小伙伴不清楚,所以我在本章最后写了案例o.O,下面我们先来了解 Promise 的一些基础知识。

Promise 特点

  1. 对象的状态不受外界的影响 

    Pending   (进行中)Fulfilled (已成功)Rejected  (已失败)复制代码
  2. 状态的固化(状态一旦发生改变就不会再改变)

    Pending -> Fulfilled
    Pending -> Rejected复制代码

  Promise 创建

  • 构造函数接收一个 executor的函数作为参数。该函数的两个参数 resolve 和 reject,它们都是函数,用来改变 Promise 对象的状态。resolve是将Promise对象的状态由‘进行中’ 变为 ‘成功’Pending -> Fulfilled,在异步操作成功时调用,并将成功的结果作为参数传递给对应的回调函数。reject是将Promise对象状态由‘进行中’ 变为 ‘失败’Pending -> Rejected,在异步操作失败时调用,并将失败的信息作为参数传递给对应的回调函数。

  • executor函数什么时候执行呢?

    let promise = new Promise((resolve, reject) => {
      console.log(1);
    })
    console.log(2);
    // 1 2 
    // 在 new 创建 promise 实例时立即调用。复制代码
  • 在Promise实例生成后,可以通过调用then()方法来绑定异步操作成功/失败的回调函数。then()方法接收两个回调函数作为参数,第一个参数必选,Promise 对象成功状态的回调函数,第二个参数可选,Promise 对象失败状态的回调函数。

    let promise = new Promise((resolve, reject) => {
      setTimeout(() =>{
        Math.random() * 100 > 60 ? resolve('及格') : reject('不及格');
      }, 300)
    })
        
    promise.then((result) => {
      console.log(result);
    }, (reason) => {
      console.log(reason);
    })复制代码
  • 执行顺序,我们发现输出的顺序是 1 -> 3 -> 2,Promise 新建后立即执行,所以先输出 1,然后通过resolve改变Promise对象的状态来调用then()方法中对应的回调函数是异步操作,所以会等待当前脚本所有的同步任务执行完成后才执行,所以先打印3,最后才是2。

    let promise = new Promise((resolve,reject) => {
      console.log(1);
      resolve(2);
    })
        
    promise.then((res) => {
      console.log(res);
    })
        
    console.log(3);
        
    // 1 3 2复制代码

与定时器异步的区别

  • 首先先简单提一下JS代码的执行过程:同步的进入主线程,异步会的进入Event Table并注册函数。当指定的事情完成时,Event Table会将这个函数移入Event Queue。主线程内的任务执行完毕为空,会去Event Queue读取对应的函数,进入主线程执行。上述过程会不断重复,也就是常说的Event Loop(事件循环)。

  • JS 异步代码中,分为宏任务与微任务,他们分别有自己的任务队列。微任务:promise、process.nextTick();宏任务:setTimeout、setInterval, Ajax,DOM事件。这里就存在一个优先级的问题,当主线程空闲时间,会先检查微任务队列,然后再走宏任务队列。所以下面的代码执行顺序就是 2 -> 4 -> 3 -> 1

    setTimeout(() => {
      console.log(1);
    }, 30)
    let promise = new Promise((resolve, reject) => {
      console.log(2);
      resolve(3);
    })
    promise.then((res) => {
      console.log(res);
    })
    console.log(4);复制代码

then方法链式调用

  • then()方法的作用是为 Promise 实例添加成功和失败状态的回到函数,会返回一个新的 Promise 实例,所以then()方法后面可以继续跟另一个then()方法进行链式调用。

    let promise = new Promise((resolve, reject) => {
      resolve(1);
    })
    promise.then((result) => {
      console.log(result);
      return 2;  
    }, (reason) => {
      console.log(reason);
    })
    .then((result) => {  // 上一次then的返回值作为这次的参数,没有则为 undefined
      console.log(`then2: ${result}`);
    })复制代码
  • 如果前一个then()方法中的回调返回的是一个 Promise 实例,这时下一个then()方法中的回调会根据前一个的 Promise 实例的状态来进行调用。

    let p = new Promise((resolve, reject) => {
      resolve('success');
    })
    p.then((result) => {
      console.log(result); // -> 'success'
      return new Promise((resolve, reject) => {
        reject(`error-.-`);
      })
    })
      .then((result) => {
        console.log(result); // -> 不走这里因为上一次返回的 Promise 状态为 Rejected
      }, (reason) => {
        console.log(reason); // -> 'error-.-'
      })复制代码

catch方法

  • 用于指定发生错误时的回调函数,就等同于then()方法的第二个回调函数。

    let promise = new Promise((resolve, reject) => {
      resolve(data);
    })
    // 因为then方法第一个参数是必选项,又用不到所以就填一个null
    promise.then(null, (reason) => {
      console.log(reason);
    })
    // === 
    promise.then((res) => {
      console.log(res);
    })
      .catch((err) => {
      console.log(err);
    })复制代码

状态固化

  • Promise 对象状态一旦发生改变,就不会再变。

    let promise = new Promise((resolve, reject) => {
      resolve('Fulfilled');  // 将状态改为 Fulfilled
      console.log(data);  // 再抛出错误也不会改变状态
    })
        
    promise.then(res => console.log(res)) 
           .catch(err => console.log(err)); // 捕获不到
    // -> Fulfilled复制代码

状态依赖

  • 当前 p2 的状态依赖于 p1 的状态,就会导致自身的状态失效。

    let p1 = new Promise((resolve, reject) => {
      setTimeout(() => {
        reject('fail')
      }, 1000)
    })
        
    let p2 = new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve(p1)
      }, 500)
    })
        
    p2.then(res => console.log(res))
      .catch(err => console.log(err)); // -> fail复制代码

all和race

  • 都是 Promise 构造器上的方法,Promise.all(),可以将多个 Promise 实例包装成一个新的Promise 实例。成功与失败的返回值是不同的,当所有的都成功时返回的结果是一个数组,失败时返回最先触发失败状态的错误信息。

  • 注意: Promise.all() 获取的成功结果的数组里的数据与传入时的顺序是一致的。

    let promise1 = new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve('peace');
      }, 1000)
    })
        
    let promise2 = new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve('and');
      }, 2000)})
        
    let promise3 = new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve('love');
      }, 300)
    })
        
    let p = Promise.all([promise1, promise2, promise3]);
        
    console.log(p);
    p.then(res => console.log(res)) // -> ["peace", "and", "love"]
     .catch(err => console.log(`reject: ${err}`));    
    复制代码
  • Promise.race(),与Promise.all()的用法相同,但是功能不同,它是获取最快返回结果的那一个,无论结果本身是成功态还是失败状态。

    let promise1 = new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve('peace');
      }, 1000)
    })
        
    let promise2 = new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve('and');
      }, 2000)})
        
    let promise3 = new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve('love');
      }, 300)
    })
        
    let p = Promise.race([promise1, promise2, promise3]);
        
    console.log(p);
    p.then(res => console.log(res)) // -> love
     .catch(err => console.log(`reject: ${err}`));复制代码

解决回调地狱问题

// 在无 Promise 之前  node环境读取文件
const fs = require('fs');

// 这种代码非常的不好维护
fs.readFile('./name.txt', 'utf-8', (err, data) => {
  if(data){
    fs.readFile(data, 'utf-8', (err, data) => {
      if(data){
        fs.readFile(data, 'utf-8', (err, data) => {
          console.log(data);
        })
      }
    })
  }
})

// Promise化管理
function readFile (path) {
  return new Promise((resolve, reject) => {
    fs.readFile(path, 'utf-8', (err, data) => {
      if(data){
        resolve(data);
      }
    })
  })
}

readFile('./name.txt').then(data => return readFile(data))
                      .then(data => return readFile(data))
                      .then(data => console.log(data))

// 将传入的函数Promise化
function promisify (fn) {
  return function (...args) {
    return new Promise((resolve, reject) => {
      fn(...args, (err, data) => {
        if(err){
          reject(err);
        }else{
          resolve(data);
        }
      })
    })
  }
}复制代码