全面理解Promise

96 阅读11分钟

什么是Promise?(异步编程的解决方案)

在JavaScript中,Promise是一种异步编程的解决方案【从语法上讲:Promise是一个对象,从它可以获取异步操作的消息】,用于处理异步操作的结果,Promise可以使异步操作更加容易管理和更可读,不需要嵌套回调函数,避免回调地狱场景出现

异步函数: js执行任务时,一次只能执行一个任务,会阻塞其他任务,而异步任务是当遇到该方法时,它不会阻塞程序的执行流程,程序会继续往下执行;当方法执行完毕后,程序能够获取执行完毕的消息或结果
回调函数: 当一个函数作为参数传入函数中,且传入的参数函数满足一定条件后执行,则这种参数函数就叫做回调函数
回调地狱: 回调函数继续进行回调

//回调地狱
setTimeout(function() {
    console.log('第一层回调');
    setTimeout(function() {
        console.log('第二层回调');
        setTimeout(function() {
            console.log('第三层回调');
            ... //n层回调, 形成了回调地狱
        }, 1000)
    }, 2000)
}, 3000)

Promise的三种状态

  1. pending【进行中】
  2. fulfilled【已成功】
  3. rejected【已失败】
    注意: Promise的状态只能从pending转换成fulfilled或rejected状态,一旦状态变更后,就不会再改变

Promise的用法

Promise的构造函数:

Promise的构造函数接受一个参数,即回调函数,且这个回调函数需传入两个参数:resolve、reject【传入的参数本身就是函数】

  • resolve:异步操作执行成功后的回调函数【把Promise的状态从进行中变为成功状态,pending-->fulfilled】
  • reject:异步操作执行失败后的回调函数【把Promise的状态从进行中变为拒绝状态,pending-->reject】
let my_promise = new Promise((resolve, reject) => {
    //异步操作,eg:接口请求...
    setTimeout(() => {
        console.log('promise里的异步函数');
        resolve(); //执行成功
    )
})

Promise回调:

1. Promise.then:
(1) 执行resolve时,Promise状态从pending变为fulfilled,会执行Promise.then方法;
(2) Promise.then方法可以接受一个回调函数作为参数,该回调函数携带一个参数,该参数就是resolve(res)返回的数据
(3) Promise.then方法也可以接受两个回调函数作为参数,第一个回调函数携带一个参数,该参数是resolve(res)返回的数据;第二个回调函数携带也携带一个参数,该参数是reject(err)返回的参数
(4) 若Promise对象的状态是rejected,且此时then方法没有设置第二个参数,就会向外抛出一个错误,使catch捕捉到
(5) then方法的返回值是一个新的promise对象,与调用then方法的不是同一个promise对象

//then只有一个回调函数作为参数
let my_promise = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('执行到resolve啦'); //执行成功
    )
}).then(res => {
    console.log(res); //执行到resolve啦
})

//then有两个回调函数作为参数
let my_promise = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('执行到resolve啦'); //执行成功
    )
}).then(res => {
    console.log(res); //执行到resolve啦
}, err => {
    console.log(err); //没有执行reject,所以该回调函数不执行
})

2. Promise.catch:
(1) 执行reject时,Promise状态从pending变为rejected,会执行Promise.catch方法;
(2) Promise.catch方法接受一个回调函数作为参数,该回调函数携带一个参数,该参数就是reject(err)返回的数据
(3) Promise.catch的作用与Promise.then方法的第二个参数效果相同,但它还可以捕捉到then中抛出的异常,不会阻塞其他任务

let my_promise = new Promise((resolve, reject) => {
    setTimeout(() => {
        reject('哎呀,要执行失败了'); //失败状态
    )
}).then(res => {
    console.log(res); //此时then中的方法不执行,因为没有执行resolve
}).catch(err => {
    console.log(err); //哎呀,要执行失败了
})

3. Promise.finally:
(1) 无论Promise的状态是resolved还是rejected都会执行其中的回调函数,只有在状态是pending时,finally的函数是不会执行的;其返回值是一个Promise,且保留原promise对象的状态和值
(2) Promise.finally是不会改变promise的状态

Promise方法:

1. Promise.all:
(1) 传入一个数组参数,数组中均是Promise,若数组中的元素不是Promise对象,则会使用Promise.resolve将其转成Promise对象
(2) Promise.all可以将多个Promise实例包装成一个新的Promise实例;成功和失败的返回值是不同的,成功时返回的是一个结果数组,失败时返回的是最先被reject失败状态的值
(3) Promise.all提供了并行执行异步操作的能力,并且在所有异步操作完成之后,统一返回所有结果;数组中所有的Promise都执行成功则成功,可以执行then方法;若数组中有一个Promise执行失败,则返回失败,执行catch方法

// 实例一
const a = new Promise(resolve => resolve('a'));
const b = new Promise(resolve => resolve('b'));
Promise.all([a, b]).then(res => {
    console.log('all', res); // ['a', 'b']
    // res返回一个数组,其元素对应传入参数Promise的返回值
})
// 实例二(成功失败案例对比)
const p1 = new Promise((resolve, reject) => {
    resolve('成功了');
})
const p2 = new Promise((resolve, reject) => {
    resolve('success');
})
const p3 = Promise.reject('error');

const p4 = Promise.reject('失败');
//成功时
Promise.all([p1, p2]).then(res => {
    console.log(res); //p1,p2都是成功态 ['成功', 'success']
}).catch(err => {
    console.log(err); //此时不执行
})

//失败时
Promise.all([p1, p3, p2, p4]).then(res => {
    console.log(res); //此时有一个失败状态,所以不走then
}).catch(err => {
    console.log(err); //p3是失败状态,且是Promise数组中返回的第一个失败状态,error
})

2. Promise.race:
(1) 用法以及传入的参数与Promise.all一样,只是返回的结果不同
(2) Promise.race返回的是执行最快的那个Promise的结果,不管结果本身是成功状态还是失败状态

const a = new Promise(resolve => {
    setTimeout(() => {
        resolve('a');
    }, 1000)
})

const b = new Promise(resolve => {
    setTimeout(() => {
        resolve('b');
    }, 2000)
})

Promise.race([a, b]).then(res => {
    console.log('race', res); //返回a,a比较先返回结果
})

Promise特性

  • 立即执行性:new Promise相当于new Function,这段代码是同步执行的,不会异步执行;而then回调方法是异步的
// Promise的立即执行性
const p = new Promise((resolve, reject) => {
    console.log('create a promise');
    resolve('success');
})

console.log('after p');

p.then(value => {
    console.log(value);
})

// 输出create a promise --> after p --> success
// 如果没有p.then(),也会输出create a promise --> after p
  • Promise状态的不可逆性:
const p1 = new Promise((resolve, reject) => {
    resolve('p1-success1');
    resolve('p1-success2');
})

p1.then(res => {
    console.log(res); //p1-success1
}).then(res => {
    console.log(res); //undefined,上一个then没有返回值
}).catch(err => {
    console.log(err); //不执行
});


const p2 = new Promise((resolve, reject) => {
    resolve('p2-success1');
    reject('p2-error1');
});

p2.then(res => {
    console.log(res); //p2-success1
}).catch(err => {
    console.log(err); //不执行,此时的状态已经是resolved,所以不执行reject函数
});


const p3 = new Promise((resolve, reject) => {
    reject('p3-error1');
    resolve('p3-success1');
});

p3.then(res => {
    console.log(res); //不执行,此时的状态已经是rejected,所以不执行resolve函数
}).catch(err => {
    console.log(err); //p3-error1
})

Promise链式调用顺序🌰

示例一:

  1. promise的then/catch方法执行后也会返回一个promise

  2. 当执行then方法时,若前面的promise已经是resolved状态,则直接将回调放入微任务队列中;若前面的promise是pending,则会将回调存储在promise内部,一直等到promise被resolve才会将回调函数推入到微任务队列中

  3. 执行then方法是同步的,而then中的回调是异步的

  4. then方法值负责注册回调,由resolve将注册的回调放入微任务队列中,由事件循环将其取出并执行

  5. 若then方法返回的promise是没有resolve函数的,取而代之只要then中回调的代码执行完毕并获得同步返回值,这个then返回的promise就算是被resolve

  6. 链式调用的第二个then是第一个then返回的promise resolve()的回调函数

new Promise((resolve, reject) => {
    console.log("promise1");   // 同步任务,立即执行
    resolve();   //此时是resolved状态,将then中的回调函数放入微任务队列中
}).then(() => {
    console.log("then11"); // 微任务队列中的第一个任务,
    new Promise((resolve, reject) => {
        console.log("promise2");
        resolve();
    }).then(() => {
        console.log("then21");
    }).then(() => {
        console.log("then23")
    })
    // 第一个promise的then中没有返回值,直接默认返回resolve()
}).then(() => {
    console.log("then12")
})
// 打印顺序:promise1 then11 promise2 then21 then12 then23

执行思路:

  1. 先执行同步任务,所以首先是 promise1
  2. 遇到resolve(),此时promise的状态为"resolved",将第一个then方法放入微任务队列中
  3. 无其他同步任务,执行微任务,此时打印"then11",new Promise()也是同步任务,所以紧跟着打印"promise2"
  4. 遇到promise2的resolve(),将promise2的第一个then中的回调函数放入微任务中,第二个then中的回调函数由于第一个then返回的promise还在pending中,所以存储在promise内部
  5. 此时promise1的同步函数执行完且没有返回值,直接resolve,所以将promise1的第二个then放入微任务队列中,promise2的第一个then的回调函数执行,并无返回值,则将promise2的第二个then的回调函数放入微任务中,所以先打印then21,接着打印then12,最后打印then23

示例二:

new Promise((resolve, reject) => {
    console.log("promise1");
    resolve();
}).then() => {
    console.log("then11");
    return new Promise((resolve, reject) => {
        console.log("promise2");
        resolve();
    }).then(() => {
        console.log("then21");
    }).then(() => {
        console.log("then23");
    })
}).then(() => {
    console.log("then12");
})
// 打印顺序:promise1 then11 promise2 then21 then23 then12

执行思路:

  1. 先执行同步任务,首先打印promise1
  2. 遇到promise1的resolve,将第一个then的回调函数放入微任务队列中
  3. 同步任务执行完,开始执行微任务,打印then11,new Promise也是同步任务,接着打印promise2
  4. promise1中的第一个then有定义新的Promise作为返回值,所以此时的promise1的第二个then中,promise状态为pending,因此还存储在promise内部
  5. promise2的new Promise中遇到了resolve(),所以将第一个then的回调函数放入微任务中,且第一个then没有返回值,所以默认执行resolve(),将第二个then的回调函数放入微任务中,并执行resolve()
  6. 此时promise2的一系列执行完毕,返回状态为resolve的Promise,所以此时将promise1的第二个then中的回调函数放入微任务队列中

示例三:

  1. 链式调用的注册是前后依赖的,比如链式调用的第二个then的注册,是需要链式调用的第一个then执行完成
  2. 变量定义的方式,注册都是同步的,即then与new Promise都是同步执行的
new Promise((resolve, reject) => {
    console.log("promise1");
    resolve();
}).then(() => {
    console.log("promise1的then1");
    let p = new Promise((resolve, reject) => {
        console.log("promise2");
        resolve();
    });
    p.then(() => {
        console.log("promise2的then1");
    });
    p.then(() => {
        console.log("promise2的then2");
    });
}).then(() => {
    console.log("promise1的then2");
});

// 顺序打印:promise1 promise1的then1 promise2 promise2的then1 promise2的then2 promise1的then2

执行思路:

  1. 先执行同步任务,打印"promise1" 2.遇到resolve,将promise1的第一个then中的回调函数放入微队列中,promise1的第二个then,由于此时的第一个then的promise状态还是pending,所以其存储在promise内部 3.执行promise1的第一个then的回调函数中的同步任务,打印promise1的then1和promise2 4."promise2的then1"和"promise2的then2"由于此时的promise2的状态已经是resolve,所以按顺序放入微任务队列中 5.此时同步任务执行完,promise1的then1没有返回值,所以默认执行resolve(),即将"promise1的then2"放入微任务队列 6.顺序执行微任务

示例四:

new Promise((resolve, reject) => {
    console.log("promise1");
    resolve();
}).then(() => {
    console.log("promise1的then1");
    new Promise((resolve, reject) => {
        console.log("promise2");
        resolve();
    }).then(() => {
        console.log("promise2的then1");
    }).then(() => {
        console.log("promise2的then2");
    });
    return new Promise((resolve, reject) => {
        console.log("promise3");
        resolve();
    }).then(() => {
        console.log("promise3的then1");
    }).then(() => {
        console.log("promise3的then2");
    })
}).then(() => {
    console.log("promise1的then2");
})

// 顺序打印:promise1 promise1的then1 promise2 promise3 promise2的then1 promise3的then1 
// promise2的then2 promise3的then2 promise1的then2

执行思路:

  1. 执行同步任务,打印promise1
  2. 遇到resolve(),将promise1的then中的回调函数放入微任务队列中,执行完同步任务,开始执行微任务队列中的then
  3. 打印promise1的then1的同步任务,new Promise也是同步任务,打印promise2,遇到promise2的resolve(),将promise2的第一个then的回调函数放入微任务队列中,第二个then由于第一个then的Promise状态还是pending,所以存储在promise内部
  4. 遇到promise1的return,且return的是一个Promise对象,所以执行promise3的同步任务,打印promise3,遇到promise3的resolve(),所以将promise3的第一个then放入微任务队列中
  5. 此时已经没有同步任务,所以执行微任务的第一个任务,即打印"promise2的then1",且其无返回值,默认执行resolve(),所以将promise2的then2放入到微任务队列中
  6. 执行微任务队列的第二个任务,即打印"promise3的then1",且执行resolve,将promise3的then2放入微任务队列中
  7. 继续执行微任务下一个任务,打印"promise2的then2"
  8. 继续执行微任务下一个任务,打印"promise3的then2"
  9. 此时promise1的then1的返回的Promise是resolve状态,所以将promise1的then2的回调函数放入微任务队列中,打印"promise1的then2"