Promise基础用法

261 阅读6分钟

1. Promise基本概念

  • ES6中新增一个内置类:Promise,可以有效处理异步编程

1.1 从一个需求讲起

  • AJAX串行需求:首先从服务器端基于/api/1获取数据,把数据作为参数基于/api/2获取其它数据,最后基于2的数据,基于/api/3获取数据
$.ajax({
  url: '/api/1',
  method: 'GET',
  dataType: 'json',
  success: function(result) {
    console.log(result)
    $.ajax({
      url: '/api/2',
      method: 'GET',
      dataType: 'json',
      success: function(result) {
        console.log(result)
      }
    })
  }
})
  • Promise管理异步编程
const api1 = () => {
  return new Promise(resolve => {
    $.ajax({
      url: '/api/1',
      method: 'GET',
      dataType: 'json',
      success(result) {
        resolve(result)
      }
    })
  })
}

api1().then(result => {
  console.log('第一次请求成功', result)
  return api2();
}).then(result => {
  console.log('第二次请求成功', result)
  return api3();
}).then(result => {
  console.log('第三次请求成功', result)
});
  • 更简便的基于Promise的写法
(async function() {
  let result = await api1();
  console.log('第一次请求成功', result)
  result = await api2();
  console.log('第二次请求成功', result)
  result = await api3();
  console.log('第三次请求成功', result)
})();

1.2 js中的异步编程

  • ajax请求
  • 事件绑定
  • 定时器
  • Promise/async/await
  • requestAnimationFrame
setTimeout(() => {
  console.log(1);
}, 1000);
console.log(2);
// 先输出 2,后输出 1

2. Promise基本用法

2.1 必传executor

  • new时,必须传递一个executor函数进来
let p1 = new Promise();
// Promise resolver undefined is not a function

2.2 new Promise的时候

2.2.1 会立即执行传递的executor函数
  • 在executor函数中一般用来管控一个异步操作
    • 不写异步操作,不改状态(不调用reslove,reject),的其他同步代码,会同步执行
    • 执行resolve/reject时,会立即更改状态信息,但是不会立即通知方法执行(有异步效果)
    • reslove改变状态是同步的,then执行回调是异步的
  • executor函数有两个参数,resolve和reject,都是函数
2.2.2 new Promise类的一个实例p1,console.log(p1)
  • 每一个promise实例都有[[PromiseState]]和[[PromiseResult]]
  • [[PromiseState]] promise的状态:pending准备/fulfilled/resolved已兑现/rejected已拒绝
  • [[PromiseResult]] promise的值:默认undefined,一般存储成功的结果或者失败的原因
  • p1.__proto__指向promise.prorotype (then/catch/finally三个方法)
2.2.3 状态变更规则
  • 执行resolve,控制实例的状态变为成功,传递的值是结果[[PromiseResult]]
  • reject,控制实例的状态变为失败,传递的值是结果[[PromiseResult]]
  • 一旦状态从pending发生改变,后边操作都无效(eg: 先resolve后reject,后操作无效)
  • 代码中有报错,会触发rejected状态,值[[PromiseResult]]是报错原因
  • 异步代码操作成功,此时通过执行resolve(),状态pending会变成resolved
2.2.4 代码执行顺序1
    1. new Promise的时候创建一个promise实例
    1. 执行executor,设置一个异步定时器
    1. 执行p1.then注入的两个方法,注入的方法会被保存起来(同步)
    1. 等待1000ms
    1. 执行定时器的回调函数,执行resolve改变promise的状态和值(resolve是异步)
    1. 基于之前then注入的两个方法,结合状态,执行一个
let p1 = new Promise(function(resolve, reject) {
  setTimeout(() => 
    reslove('ok')
  }, 1000)
})
p1.then(result=> {
  // p1状态为fulfilled时候,此函数执行,result->[[PromiseResult]]
}, reason=> {
  // p1状态为rejected时候,此函数执行,reason->[[PromiseResult]]
})
2.2.5 代码执行顺序2
    1. 输出1
    1. reslove修改状态和值,通知then, 但是此时.then还未执行注入,不知道通知谁执行,把通知操作先保存,内部是放到等待任务队列中,这个操作本身是异步的,new promise是同步的reslove通知注入执行是异步的,此时p1已经是成功了,暂时存储
    1. 输出2
    1. 开始注入方法,保存
    1. 输出3
    1. 异步队列,执行注入.then执行
let p1 = new Promise(function(resolve, reject) {
  console.log(1)
  resolve('OK')
  console.log(2)
})
p1.then(result=> {
  console.log('成功->', result)
}, reason=> {
  console.log('失败->', reason)
})
console.log(3)
2.2.6 代码执行顺序3
  • resolve是异步的,不会立即处理,会放到等待队列

  • 1s后修改状态和值,存入异步队列,此时方法已经注入好了

  • 同步先输出1

  • 执行then,输出2

let p1 = new Promise(resolve => {
  setTimeout(()=> {
    resolve('OK')
    console.log(1)
  }, 1000)
})
p1.then(result=> {
  console.log(2)
})

3. Promise更多用法

let p1 = new Promise(resolve => {
  resolve('OK')
})
// 上面可以简写为下面
// 创建一个状态为成功的promise实例
// let p1 = Promise.resolve('OK')

let p2 = p1.then(result => {
  console.log('成功->', result)
}, reason => {
  console.log('失败->', reason)
})

// p2是一个全新的promise实例

p2.then(result => {
  console.log('成功->', result)
}, reason => {
  console.log('失败->', reason)
})
  • 执行.then返回一个全新的promise实例

3.1 promise实例状态和值的分析

3.1.1 new Promise出来的实例(p1)
  • resolve/reject的执行控制其状态以及结果
  • executor函数执行失败,导致状态失败,结果是报错原因
3.1.2 执行.then返回的新的promise实例(p2)
  • 上一个p1.then注入的方法,无论哪个方法执行,只要执行不报错,新实例的状态就是fulfilled,执行报错,新实例的状态就是rejected,并且新实例p2的promise值,是方法返回的值
  • 如果方法执行,返回的是一个新的promise实例,则此实例最后的状态,决定了.then返回的成功和失败,结果也是一样的
let p1 = new Promise(resolve => {
  resolve('OK')
})
let p2 = p1.then(result => {
  console.log('成功->', result)
  return 10
}, reason => {
  console.log('失败->', reason)
})
p2.then(result => {
  console.log('成功->', result)
}, reason => {
  console.log('失败->', reason)
})
// p1成功,输出 成功,ok
// p2是p1返回的成功,输出 成功,10
let p1 = new Promise((resolve, reject) => {
  reject('No')
})
let p2 = p1.then(result => {
  console.log('成功->', result)
  return 10
}, reason => {
  console.log('失败->', reason)
  return 20
})
p2.then(result => {
  console.log('成功->', result)
}, reason => {
  console.log('失败->', reason)
})
// p1失败,输出 失败,No
// p2是p1返回的成功,输出 成功,20
let p1 = Promise.resolve('OK')
let p2 = p1.then(result => {
  console.log('成功->', result)
  return Promise.reject('No')
}, reason => {
  console.log('失败->', reason)
  return 20
})
p2.then(result => {
  console.log('成功->', result)
}, reason => {
  console.log('失败->', reason)
})
// p1成功,输出 成功,ok
// p2,输出 失败,No
3.1.3 顺延
  • 对于失败的promise实例,如果没有编写的方法处理其结果,则会抛出异常,但不会阻碍其他代码执行
  • 如果失败,.then最后接上一个的失败状态
  • 在.then注入的方法的时候,如果其中某个方法没有传递,则会顺延到下一个then中具备相同状态需要执行的函数上
Promise.reject('No').then(result => {
  console.log('成功', result)
  return 10;
}).then(null, reason => {
  console.log('失败', reason)
})
Promise.resolve('ok').then(null, reason=> {
   console.log('失败', reason)
}).then(result => {
  console.log('成功', result)
})
  • 捕捉最后的错误用catch代替
Promise.resolve('ok').then(null, reason=> {
   console.log('失败', reason)
}).then(result => {
  console.log('成功', result)
}).then(result => {
  console.log('成功', result)
}).catch(reason => {
 console.log('失败', reason)
})

3.2 Promise.all & Promise.race

  • Promise.all 等待所有promise实例都成功,整体返回的状态才成功,一个失败,整体失败
  • Promise.race 看多个实例谁先处理完,先处理完的状态,无论失败成功,就是最后整体状态
const api1 = () => {}
const api2 = () => {}
const api3 = () => {}
const fn = () => {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(100)
    }, 1000)
  })
}

const AA = Promise.reaolve('AA')

let p  = Promise.all([api1(), api2(), api3(), AA, fn(), 10]);
p.then(results = > {
  // 每个结果拼成的数组,按照之前设定的顺序依次存储结果,不管谁先返回
  console.log('所有结果', results)
}).catch(reason => {
  // 只要一个失败,整体失败,走这里,立即结束处理,谁失败,记录失败的原因
  console.log('err->', reason)
})