【JavaScript】Promise 的使用

Promise 简介

  • 在 JS 中执行异步操作的方案 —— Promise
  • Promise 是一个构造函数,用来封装异步操作,可以获取异步操作的返回值
使用 Promise 执行异步操作的优点:
  1. 比传统的解决方案(回调函数)更灵活、更强大
    • 回调函数实现异步:必须在执行异步任务之前,指定回调函数
    • Promise 实现异步:执行异步任务 → 返回 Promise 实例 → 给 Promise 实例绑定回调函数
  2. 支持链式调用,避免 “回调地狱”(回调地狱:疯狂嵌套回调函数。不便于阅读、处理)
    代价:代码量翻倍
使用 Promise 执行异步操作的缺点:
  1. 无法取消 Promise 对象,一旦新建,就会立即执行,且无法中断
  2. 如果不设置函数参数,Promise 对象内部抛出的错误,不会反应到外部
  3. 当处于 pending 状态时,无法得知目前进展到哪一个阶段(刚刚开始 or 即将完成)

构造函数 Promise

Promise(executor)

回调函数 executor:也叫 “执行器”,格式为 (resolve, reject) => {}

  • 如果不调用回调函数 resolve / reject,则 Promise 实例的状态将一直是 pending
  • 回调函数 resolve:调用 resolve 会改变 Promise 实例的状态 pending → fulfilled
  • 回调函数 reject:调用 reject 会改变 Promise 实例的状态 pending → rejected
let pro0 = new Promise(() => {});
console.log("pro0", pro0); // pro0 Promise {<pending>}

let pro1 = new Promise((resolve) => {
    resolve();
});
console.log("pro1", pro1); // pro1 Promise {<fulfilled>: undefined}

let pro2 = new Promise((resolve, reject) => {
    reject();
});
console.log("pro2", pro2); // pro2 Promise {<rejected>: undefined}
  • 执行器 executor 是同步执行的
console.log("before executor"); // before executor
new Promise(() => {
    console.log("executor"); // executor
});
console.log("after executor"); // after executor

上例输出顺序:before executorexecutorafter executor
如果执行器是异步执行的,则输出顺序会是:before executorafter executorexecutor

Promise 实例的值
  • 调用 resolve(val) / reject(val) 变更状态时,传入的参数数据 val 就是该 Promise 实例的值

    即 resolve & reject 都可接收 1 个参数,作为 Promise 实例的值

  • Promise 实例的值可通过查看属性 [[PromiseResult]] 得知

let pro1 = new Promise((resolve) => {
    resolve("pro1 resolve");
});
console.log("pro1", pro1); // pro1 Promise {<fulfilled>: 'pro1 resolve'}

let pro2 = new Promise((resolve, reject) => {
    reject("pro2 reject");
});
console.log("pro2", pro2); // pro2 Promise {<rejected>: 'pro2 reject'}
Promise 实例的状态
  1. 待定 pending:进行中, 此时异步操作尚未完成

  2. 完成 fulfilled:表示异步操作成功

  3. 拒绝 rejected:表示异步操作失败

  4. 调用 resolve 方法,Promise 实例的状态从 pending → fulfilled

  5. 调用 reject 方法,Promise 实例的状态从 pending → rejected

  6. 抛出错误,Promise 实例的状态从 pending → rejected

let pro1 = new Promise((resolve, reject) => {
    resolve("value");
});
console.log(pro1); // Promise {<fulfilled>: 'value'}

let pro2 = new Promise((resolve, reject) => {
    reject("reason");
});
console.log(pro2); // Promise {<rejected>: 'reason'}

let pro3 = new Promise((resolve, reject) => {
    throw "error";
});
console.log(pro3); // Promise {<rejected>: 'error'}
  • Promise 实例的状态可通过查看属性 [[PromiseState]] 得知
  • 状态只能从 pending → fulfilled / rejected,且状态一旦变更就不会再改变
let pro1 = new Promise((resolve, reject) => {
    resolve(); // 写在前面,生效
    reject(); // 后面的不会生效
});
console.log("pro1", pro1); // pro1 Promise {<fulfilled>: undefined}

let pro2 = new Promise((resolve, reject) => {
    resolve(); // 写在前面,生效
    throw "error"; // 后面的不会生效
});
console.log("pro2", pro2); // pro2 Promise {<fulfilled>: undefined}

resolve & reject

Promise.resolve(value)

  • 用于创建 fulfilled 状态的 Promise 实例

① 参数 value 是 Promise 实例 → 直接返回该实例

let p1 = new Promise(resolve => {
    resolve('error');
});
console.log(p1); // Promise {<rejected>: 'error'}

let p2 = Promise.resolve(p1);
console.log(p2); // Promise {<rejected>: 'error'}

console.log(p1 === p2); // true

② 参数 value 不是 Promise 实例 → 返回 fulfilled 状态的 Promise 实例,值为参数值

let pro = Promise.resolve(123);
console.log(pro); // Promise {<fulfilled>: 123}

③ 无参数 → 返回 fulfilled 状态的 Promise 实例,值为 undefined

let pro = Promise.resolve();
console.log(pro); // Promise {<fulfilled>: undefined}

Promise.reject(reason)

  • 用于创建 rejected 状态的 Promise 实例
  • 参数 reason 为该 Promise 实例的值,传入什么,Promise 实例的值就是什么
    如果不设置参数,则该 Promise 实例的值为 undefined
let pro = new Promise((resolve, reject) => {
    resolve("pro resolve");
});

let pro1 = Promise.reject(pro);
console.log("pro1", pro1); // pro1 Promise {<rejected>: Promise}

let pro2 = Promise.reject("reason");
console.log("pro2", pro2); // pro2 Promise {<rejected>: 'reason'}

let pro3 = Promise.reject();
console.log("pro3", pro3); // pro3 Promise {<rejected>: undefined}

then & catch & finally

  • 构造函数 Promise(executor) 的执行器 executor同步执行的
  • 但 Promise 实例的 then / catch / finally 操作是异步执行的,属于异步操作里面的微任务

Promise.prototype.then

Promise.prototype.then(onResolve, onReject)
  1. 回调函数 onResolve:处理状态为 fulfilled 的 Promise 实例

  2. 回调函数 onReject:处理状态为 rejected 的 Promise 实例

    onReject & onResolve 都可接收 1 个参数,参数值为调用该方法的 Promise 实例的值

  • onResolve & onReject 会返回新的 Promise 实例
  • 因为是 Promise.prototype 上的方法,所以可以通过 Promise 实例直接调用
let p1 = Promise.resolve("p1 fulfilled");
p1.then(value => {
    console.log("value", value); // value p1 fulfilled
});

let p2 = Promise.reject("p2 rejected");
p2.then(null, reason => {
    console.log("reason", reason); // reason p2 rejected
});
关于 then 方法返回的 Promise 实例
  1. return Promise 对象
let pro = Promise.resolve('pro fulfilled');
let proThen = pro.then(value => {
    console.log("value", value); // value pro fulfilled
    return Promise.reject('superman');
}); // Promise {<rejected>: 'superman'}
  1. return 非 Promise 对象:返回 fulfilled 状态的 Promise 实例,值为 return 的数据值
let pro = Promise.resolve('pro fulfilled');
let proThen = pro.then(value => {
    console.log("value", value); // value pro fulfilled
    return 'superman';
}); // Promise {<fulfilled>: 'superman'}
  1. 没有 return 语句:返回 fulfilled 状态的 Promise 实例,值为 undefined
let pro = new Promise((resolve) => {
    resolve('pro fulfilled');
});
let proThen = pro.then(value => {
    console.log("value", value); // value pro fulfilled
}); // Promise {<fulfilled>: undefined}
  1. 抛出错误:返回 rejected 状态的 Promise 实例,值为抛出的错误信息
let pro = Promise.resolve('pro fulfilled');
let proThen = pro.then(value => {
    console.log("value", value); // value pro fulfilled
    throw 'error';
}); // Promise {<rejected>: 'error'}

Promise.prototype.catch

Promise.prototype.catch(onReject)
  1. 回调函数 onReject:处理状态为 rejected 的 Promise 实例

    参数函数可接收 1 个参数,参数值为调用该方法的 Promise 实例的值

  • onReject 会返回新的 Promise 实例
  • catchthen 的语法糖,相当于 then(null, onRejected)
let pro = new Promise((resolve, reject) => {
    reject('error');
});
pro.catch(reason => {
    console.log("reason", reason); // reason error
});

Promise.prototype.finally

  • 不论状态为 fulfilled 还是 rejected,Promise 实例都会执行 finally 语句
  • 但是 pending 状态的 Promise 实例不会执行 finally 语句
  • finally 方法不接收参数
let pro1 = Promise.resolve();
pro1.finally(_ => {
    console.log("pro1 resolve"); // pro1 resolve
});

let pro2 = Promise.reject();
pro2.finally(_ => {
    console.log("pro2 reject"); // pro2 reject
});

let pro3 = new Promise(() => {});
pro3.finally(_ => {
    console.log("pro3 pending");
});
  • finally 方法会返回新的 Promise 实例,新的 Promise 实例的状态和值与调用 finally 的 Promise 实例一致
  • 即使写了新的 return 语句也没有用
let pro = Promise.resolve('pro');
console.log("pro", pro); // pro Promise {<fulfilled>: 'pro'}

let proThen = pro.finally(_ => {
    console.log("finally"); // finally
    return Promise.resolve("finally"); // return 语句无效
}); // proThen Promise {<fulfilled>: 'pro'}

需要搞懂

① 改变 Promise 状态和执行回调函数,谁先执行
  • 同步改变状态:先改变状态、后执行回调函数;异步改变状态:先执行回调函数,后改变状态
let pro1 = Promise.resolve();

let proThen = pro1.then(_ => { // 执行 then 方法属于异步操作
    console.log("pro1 resolve"); // pro1 resolve
    return "then";
});

console.log("proThen", proThen); // proThen Promise {<pending>}

如果直接在 then 方法后面打印 proThen,得到的是 proThen Promise {<pending>}
如果在控制台打印 proThen,得到的是 Promise {<fulfilled>: 'then'}
这是因为,Promise 实例的状态是通过异步操作改变的,无法通过同步操作获取改变后的状态

  • 此时可以通过异步操作获取改变后的状态:
let pro1 = Promise.resolve();

let proThen = pro1.then(_ => {
    console.log("pro1 resolve"); // pro1 resolve
    return "then";
});

setTimeout(() => {
    console.log("proThen", proThen); // proThen Promise {<fulfilled>: 'then'}
}, 0);
② 多个回调的执行
  • 如果给 Promise 实例制订了多个回调函数,会按顺序执行所有的回调函数
let pro = Promise.resolve('data');

pro.then(value => {
    console.log(value + 1); // data1
});

pro.then(value => {
    console.log(value + 2); // data2
});
③ 链式调用
  • 因为 then / catch / finally 返回的还是一个 Promise 实例,所以可以链式调用
let pro = Promise.resolve('first');

pro.then(val1 => {
    console.log("val1", val1); // val1 first
    return "second";
}).then(val2 => {
    console.log("val2", val2); // val2 second
}).then(val3 => {
    console.log("val3", val3); // val3 undefined
});

上例中,第 1 个 then 返回了 "second",所以 val2second;第 2 个 then 没有返回值,所以 val3undefined

let pro = Promise.reject('first');

pro.then(val1 => {
    console.log("val1", val1);
    return "second";
}).catch(reason => {
    console.log("reason", reason); // reason first
}).then(val2 => {
    console.log("val2", val2); // val2 undefined
});

上例中,因为 pro 的状态是 rejected,所以跳过第一个 then 方法,直接跳到了 catch 方法执行;
因为 catch 方法没有 return 语句,相当于 return Promise.resolve(),所以 val2undefined

  • 在链式调用中,没有按照规则传入回调函数的 then、catch、finally 方法,都会被直接跳过:
let p1 = Promise.resolve('p1');

p1.then(res1 => {
    console.log("res1", res1); // res1 p1
}).then(res2 => {
    console.log("res2", res2); // res2 undefined
    return 'string';
}).then().then('我是字符串').then(res3 => { // 这里有两个没写回调函数的 then 方法,会被直接跳过
    console.log("res3", res3); // res3 string    // 这里 res3 接收的是前面的合法返回值
});
④ Promise 异常穿透

使用 Promise 的链式调用时,可以在最后设置 catch 方法,并指定失败的回调函数
这样,前面任何操作出了异常,都会传到最后的 catch 中,执行其回调函数

let pro = Promise.resolve('pro');

pro.then(val1 => {
    console.log("val1", val1); // val1 pro
    return Promise.reject("reject")
}).then(val2 => {
    console.log("val2", val2);
    return "then2' return";
}).catch(reason => {
    console.log("reason", reason); // reason reject
});

上例中,因为第一个 then 方法返回的是 rejected 状态的 Promise 实例,所以不会走后面的 then 方法
这样,就直接跳到了最后的 catch 方法中

let pro = Promise.resolve('ok');

pro.then(val1 => {
    console.log("val1", val1); // val1 ok
    throw 'error';
}).then(val2 => {
    console.log("val2", val2);
}).catch(reason => {
    console.log("reason", reason); // reason error
});

上例中,因为第 1 个 then 方法抛出了错误,所以不会执行第二个 then 方法,直接跳到了最后的 catch 方法

⑤ 中断 Promise 链
  • 中断 Promise 链,就是说在使用 then 的链式调用时,在中间断开,不再执行后面的回调函数
  • 办法:在回调函数中,返回一个 pendding 状态的 Promise 对象
let pro = Promise.resolve('ok');

pro.then(val1 => {
    console.log("val1", val1); // val1 ok
    return new Promise(() => {});
}).then(val2 => {
    console.log("val2", val2);
}).catch(reason => {
    console.log("reason", reason);
});

上述代码中,因为第 1 个 then 返回 pendding 状态的 Promise 对象,所以 Promise 链没有继续往下执行

all & race & allSettled

Promise.all(proArr)

  • 等最的接⼝返回数据后,⼀起得到所有接口的数据
  • 返回新的 Promise 实例
  1. proArr:Promise 实例对象组成的数组
  2. proArr 中所有的 Promise 实例的状态都为 fulfilled,则返回状态为 fulfilled 的 Promise 实例
    此时该实例的值为:proArr 中所有 Promise 实例的值组成的数组
let p1 = new Promise(resolve => {
    setTimeout(() => {
        resolve('resolve p1');
    }, 500);
});
let p2 = new Promise(resolve => {
    setTimeout(() => {
        resolve('resolve p2');
    }, 1000);
});
let p3 = new Promise(resolve => {
    setTimeout(() => {
        resolve('resolve p3');
    }, 1500);
});

Promise.all([p1, p3, p2]).then(res => {
    console.log("res", res); // res ['resolve p1', 'resolve p2', 'resolve p3']  ( 1.5s 后输出 )
});
  1. 如果 proArr 中存在状态为 rejected 的 Promise 实例,则返回状态为 rejected 的 Promise 实例
    此时该实例值为:proArr 中,第 1 个状态为 rejected 的 Promise 实例的值
let p1 = new Promise(resolve => {
    setTimeout(() => {
        resolve('resolve p1');
    }, 1000);
});
let p2 = new Promise((resolve, reject) => {
    setTimeout(() => {
        reject('reject p2');
    }, 2000);
});
let p3 = new Promise(resolve => {
    setTimeout(() => {
        reject('reject p3');
    }, 10000);
});

Promise.all([p1, p3, p2]).catch(err => {
    console.log("err", err); // err reject p2  ( 2s 后输出 )
});

Promise.allSettled(proArr)

  • 等最的接⼝返回数据后,⼀起得到所有接口的数据

  • 返回新的 Promise 实例

  • proArr:Promise 实例对象组成的数组

  • 不论 proArr 中 Promise 实例的状态,都返回状态为 fulfilled 的 Promise 实例
    此时该实例的值为:proArr 中所有的 Promise 实例对象

let p1 = new Promise(resolve => {
    setTimeout(() => {
        resolve('resolve p1');
    }, 1000);
});
let p2 = new Promise((resolve, reject) => {
    setTimeout(() => {
        reject('reject p2');
    }, 2000);
});
let p3 = new Promise((resolve, reject) => {
    setTimeout(() => {
        reject('reject p3');
    }, 3000);
});
let pas = Promise.allSettled([p1, p2, p3]).then(valArr => {
    valArr.forEach((item, index) => {
        console.log(`item ${index+1}: `, item);
        // item 1:  {status: 'fulfilled', value: 'resolve p1'}
        // item 2:  {status: 'rejected', reason: 'reject p2'}
        // item 3:  {status: 'rejected', reason: 'reject p3'}
    });
});

Promise.race(proArr)

  • 等最的接口返回数据后,得到该接口的数据
  • 返回新的 Promise 实例
  1. proArr:Promise 实例组成的数组
  2. proArr 中,第一个改变 pendding 状态的 Promise 实例是什么状态,就返回什么状态的 Promise 实例
    此时,返回的 Promise 实例的值为第 1 个改变 pendding 状态的 Promise 实例的值
let p1 = new Promise(resolve => {
    setTimeout(() => {
        resolve('resolve p1');
    }, 500);
});
let p2 = new Promise((resolve, reject) => {
    setTimeout(() => {
        reject('reject p2');
    }, 1000);
});
let p3 = new Promise(() => {
    setTimeout(() => {
        throw "error p3"; // 抛出异常
    }, 0);
});

Promise.race([p1, p2, p3]).then(value => {
    console.log("value", value); // value resolve p1
}).catch(reason => {
    console.log("reason", reason);
});
  • 抛出异常的 Promise 实例不会被 Promise.race 处理
  • 上例中,第一个改变 pendding 状态的是 p1;即使后面有 rejected 状态的 Promise 实例,也不会再处理
let p1 = new Promise(resolve => {
    setTimeout(() => {
        resolve('resolve p1');
    }, 1500);
});
let p2 = new Promise((resolve, reject) => {
    setTimeout(() => {
        reject('reject p2');
    }, 1000);
});
let p3 = new Promise(() => {
    setTimeout(() => {
        throw "error p3";
    }, 500);
});

Promise.race([p1, p2, p3]).then(value => {
    console.log("value", value);
}).catch(reason => {
    console.log("reason", reason); // reason reject p2
});
  • 上例中,第一个改变 pendding 状态的是 p2

案例

fs 配合 Promise 异步读取文件
const fs = require('fs');

// // ① 使用回调函数完成异步操作(异步操作的传统解决方案)
// fs.readFile('./demo.html', (err, data) => {
//     if (err) throw err;
//     console.log(data + '')
// });

// ② 使用 Promise 完成异步操作
let pro = new Promise((resolve, reject) => {
    fs.readFile('./demo.html', (err, data) => {
        if (err) reject(err);
        resolve(data);
    });
});

pro.then(value => {
    console.log(value + '');
}, reason => {
    console.log(reason);
});
  • 将 fs 封装成 Promise 对象来使用
const fs = require('fs');

function myReadFile(path) {
    return new Promise((resolve, reject) => {
        fs.readFile(path, (err, data) => {
            if (err) reject(err);
            resolve(data);
        });
    });
}

myReadFile('./demo.html').then(value => { // 读取文件
    console.log(value + '')
}, reason => {
    console.log(reason);
});

每次都要自己封装,略显麻烦。util.promisify 应运而生

  • util.promisify 的作用:将函数封装成 Promise 实例

使用 util.promisify 将 fs 封装成 Promise 对象

const util = require('util');
const fs = require('fs');

let myReadFile = util.promisify(fs.readFile);

myReadFile('./demo.html').then(value => { // 读取文件
    console.log(value + '')
}, reason => {
    console.log(reason);
});

是不是超级方便!

使用 reduce 封装 Promise
function queue(numArr) {
    numArr.reduce((promise, n) => {
        return promise.then(_ => {
            return new Promise(resolve => {
                setTimeout(() => {
                    console.log(n);
                    resolve();
                }, 1000);
            });
        });
    }, Promise.resolve());
}

queue([1, 2, 3, 4, 5]);
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

JS.Huang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值