【Promise基础】

220 阅读8分钟

一、Promise是什么?

1. 理解概念

  • 抽象来说,Promise是JS中进行异步编程的新的解决方案(旧方案是通过回调函数)

  • 具体表达:

    ① Promise是一个构造函数

    ②Promise对象用来封装一个异步操作,并可以获取其成功/失败的结果

  • 引用阮一峰老师的话: 所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。

2. Promise状态

  • 对象的状态不受外界影响。 有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。
  • 一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果。

3. Promise对象的值

实例对象中PromiseResult保存Promise成功或者失败的结果值。通过调用resolve/reject来改变结果值和状态。

4. Promise基本流程

Promise流程.png

二、 为什么要用Promise?

1. 指定回调函数的方式更加灵活

  • 旧的方式:必须在启动异步任务之前指定回调
  • Promise:启动异步任务 => 返回Promise对象 => 给Promise对象绑定回调函数(甚至可以指定多个回调)

2. 支持链式调用,解决回调地狱问题

  • 什么是回调地狱?

    有时候,我们需要拿到一个异步任务的结果,才能继续下一个异步任务,也就是回调函数嵌套调用,外部回调函数异步执行的结果是嵌套的回调函数执行的条件。

  • 回调地狱的缺点?

    不便于阅读,不便于异常处理。

回调地狱.jpg

  • 通过Promise的链式调用可以解决这个问题

  • 终极解决方案:async/await

三、Promise的使用

1. API

(1) Promise构造函数:Promise(excutor) {}

  • excutor函数:执行器 (resolve, reject) => {}

  • resolve函数:Promise内部定义成功时我们调用的函数,改变状态为成功,并返回成功结果 value => {}

  • reject函数:Promise内部定义失败时我们调用的函数,改变状态为失败,并返回失败结果 reason => {}

    说明:excutor 会在 Promise 内部立即同步调用,异步操作在执行器中执行

// 先输出111,再输出222,,说明Promise构造函数的参数(执行器函数)是在Promise内部立即同步调用的
let p1 = new Promise((resolve, reject) => {
    // 同步调用    
    console.log('111');
})
console.log('222');

(2) Promise.prototype.then 方法: (onResolved, onRejected) => {}

  • onResolved 函数: 成功的回调函数 (value) => {}
  • onRejected 函数: 失败的回调函数 (reason) => {} 说明: 指定用于得到成功 value 的成功回调和用于得到失败 reason 的失败回调 返回一个新的 promise 对象

(3) Promise.prototype.catch 方法: (onRejected) => {}

  • onRejected 函数: 失败的回调函数 (reason) => {} 说明: then()的语法糖, 相当于: then(undefined, onRejected)

(4) Promise.resolve 方法: (value) => {} 用于快速得到一个Promise对象

  • value: 成功的数据或 promise 对象 说明: 返回一个成功/失败的 promise 对象
let p1 = Promise.resolve(521);
console.log(p1); // 返回一个promise对象,状态为成功,且值为521
// 如果传入的参数为 非promise类型的对象,则返回的结果为成功的promise对象
// 如果传入的参数为 Promise 对象,则参数的结果决定了 resolve 的结果
let p2 = Promise.resolve(new Promise((resolve, reject) => {
    // resolve('OK');
    reject('error')
}))
console.log(p2); // 如果传入的Promise为成功,则p2也为成功,值为内部成功的值,如果传入的Promise为失败,则p2为失败,值为内部失败的值
p2.catch(reason => {
    console.log(reason); // 'error'
});

(5) Promise.reject 方法: (reason) => {}

  • reason: 失败的原因 说明: 返回一个失败的 promise 对象
let p = Promise.reject(521);
let p2 = Promise.reject('iloveyou');
let p3 = Promise.reject(new Promise((resolve, reject) => {
    resolve('OK');
}));
console.log(p); // 状态为失败,且失败的值为521
console.log(p2); // 状态为失败,且失败的值为'iloveyou'
console.log(p3); // 即使传入参数为成功的Promise,状态仍然为失败,失败的值为传入的那个Promise

(6) Promise.all 方法: (promises) => {}

  • promises: 包含 n 个 promise 的数组 说明: 返回一个新的 promise, 只有所有的 promise 都成功才成功, 只要有一个失败了就 直接失败
let p1 = new Promise((resolve, reject) => {
resolve('OK');
})
let p2 = Promise.resolve('Sucess');
let p3 = Promise.resolve('Oh Yeah');
let p4 = Promise.reject(123);
let p5 = Promise.reject(321)
​
// 
const result = Promise.all([p1, p2 ,p3]); // 传入的Promise数组都为成功
console.log(result); // 则返回的Promise状态也为成功,成功的值为所有Promise成功结果值组成的数组['OK', 'Sucess', 'Oh Yeah']const res = Promise.all([p1,p2,p3,p4,p5]); // 传入的p4,p5为失败的Promise
console.log(res); // 只要传入有一个失败,则返回的Promise状态为失败,失败的值为第一个失败的Promise的值123

(7) Promise.race 方法: (promises) => {}

  • promises: 包含 n 个 promise 的数组 说明: race有竞赛的意思,这个函数返回一个新的 promise, 传入promise数组中第一个完成的 promise 的结果状态就是最终的结果状态
let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('OK');
}, 1000);
})
let p2 = Promise.resolve('Sucess');
let p3 = Promise.resolve('Oh Yeah');
​
const result = Promise.race([p1,p2,p3]); 
console.log(result); //在p1没有包裹定时器之前很明显p1先改变状态,所以此时返回promise为成功,且值为'OK'
// 当p1包裹定时器之后,p2先改变状态,所以返回的promise为p2状态成功,值为'Sucess'

2. Promise的几个关键问题

(1) 如何改变 promise 的状态?

  • resolve(value): 如果当前是 pending 就会变为 resolved
  • reject(reason): 如果当前是 pending 就会变为 rejected
  • 抛出异常: 如果当前是 pending 就会变为 rejected
// 改变promise状态的三种方式
let p = new Promise((resolve, reject) => {
    // 1. resolve函数
    // resolve('ok'); // pending => fulfilled(resolved)
    // 2. reject函数
    // reject('error');  // pending => rejected
    // 3. 抛出错误
    throw '出问题了';  // pending => rejected 值为 '出问题了'
})
console.log(p);

(2) 一个 promise 指定多个成功/失败回调函数, 都会调用吗?

  • 当 promise 改变为对应状态时都会调用
let p = new Promise((resolve, reject) => {
    resolve('OK');
});
// 指定回调 - 1 
p.then(value=>{
    console.log(value);
});
// 指定回调 - 2
p.then(value=>{
    alert(value);
})
// p中指定resolve改变状态为成功,则成功回调1、2都会执行,即弹窗,也console.log,
// 如果注释掉resolve('OK'),Promise状态为pending,则两个成功回调均不执行

(3) 改变 promise 状态和指定回调函数谁先谁后?

  • 都有可能, 正常情况下是先指定回调再改变状态, 但也可以先改状态再指定回调
  • 如何先改状态再指定回调? ① 在执行器中直接调用 resolve()/reject() ② 延迟更长时间才调用 then()
  • 什么时候才能得到数据? ① 如果先指定的回调, 那当状态发生改变时, 回调函数就会调用, 得到数据 ② 如果先改变的状态, 那当指定回调时, 回调函数就会调用, 得到数据

(4) promise.then()返回的新 promise 的结果状态由什么决定?

  • 简单表达: 由 then()指定的回调函数执行的结果决定
  • 详细表达: ① 如果抛出异常, 新 promise 变为 rejected, reason 为抛出的异常 ② 如果返回的是非promise的任意值, 新promise变为resolved, value为返回的值 ③ 如果返回的是另一个新promise, 此promise的结果就会成为新promise的结果
let p = new Promise((resolve, reject) => {
    resolve('OK');
});
// 执行then方法
let result = p.then(value=>{
​
    // 1. 抛出错误 , then返回的promise也是错误,值为'出问题了'
    // throw '出问题了';
    // 2. 返回结果是非Promise对象 then返回的promise为正确,值为return的521
    // return 521;
    // 3. 返回结果是Promise对象  // then返回的值根据下面的Promise决定,正确则正确,错误则也错误,值相同。
    return new Promise((resolve, reject) => {
        // resolve('success');
        reject('error');
    })
})
console.log(result);

(5) promise 如何串连多个操作任务?

  • 由于promise 的 then()返回一个新的 promise, 因此 then()可以链式调用
  • 通过 then 的链式调用串连多个同步/异步任务
let p = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('OK');
    }, 1000);
})
​
p.then(value => {
    return new Promise((resolve, reject) => {
        resolve('success');
    });
}).then(value => {
    console.log(value); // 'success'
}).then(value => {
    console.log(value); // undefined  , 由于上一个then回调函数没指定返回值,默认返回undefined(非Promise对象),上一个then返回成功的promise,值为undefined
})

(6) promise 异常传透是什么?

  • 当使用 promise 的 then 链式调用时, 可以在最后指定失败的回调,
  • 前面任何操作出了异常, 都会传到最后失败的回调中处理
let p = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('OK');
        // reject('Err');
    }, 1000);
})
​
p.then(value => {
    // console.log(111);
    throw '失败啦!'
}).then(value => {
    console.log(222);
}).then(value => {
    console.log(333);
}).catch(reason => {
    console.warn(reason); // 如果第一个Promise错误,返回'Err',如果第二个Promise 抛出异常,返回'失败啦!'
})
// 异常穿透:前面的Promise如果状态错误,,可以穿透到最后一个promise对象再来处理

(7) 如何中断 promise 链?

  • 当使用 promise 的 then 链式调用时, 在中间中断, 不再调用后面的回调函数
  • 办法: 在回调函数中返回一个 pendding 状态的 promise 对象
let p = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('OK');
    }, 1000);
})
​
p.then(value => {
    console.log(111);
    // 有且只有一种方式中断Promise链, 就是返回一个pendding状态的Promise对象,
    // 因为这样then返回的也是pending状态的promise,这样后面的then就都不会执行了,因为promise状态没有改变
    // then方法中指定的回调只有在promise状态改变之后才会执行
    return new Promise(() => {});
}).then(value => {
    console.log(222);
}).then(value => {
    console.log(333);
}).catch(reason => {
    console.warn(reason); 
})