【JavaScript】Promise 的使用

Promise 的使用

Promise 是一套处理异步场景的规范,能避免回调地狱的产生,使代码更清晰简洁。

这套规范最早诞生于前端社区,并被命名为 Promise A+ 规范。


Promise A+ 规定:

  1. 所有的异步场景,都可以看作是一个异步任务,每个异步任务,在 JS 中应该表现为一个对象,该对象称之为 Promise 对象,也叫做任务对象。
  2. 每个任务对象应该有两个阶段、三个状态:
    1. 未决阶段 (unsettled) :挂起状态 pending
    2. 已决阶段 (settled) :完成状态 fulfilled、失败状态 rejected

Promise API:new Promise(executor)

参数 executor 称为执行器,为一回调函数 (resolve, reject) => {}
调用 resolve,Promise 实例的状态会从 pending 变为 fulfilled;
调用 reject,Promise 实例的状态会从 pending 变为 rejected;
如果不调用 resolve / reject,则 Promise 实例的状态将一直是 pending。

const pro1 = new Promise(() => {});
console.log('pro1', pro1); // pro1 Promise { <pending> }

const pro2 = new Promise(resolve => resolve());
console.log('pro2', pro2); // pro2 Promise { <fulfilled>: undefined }

const pro3 = new Promise((_, reject) => reject());
console.log('pro3', pro3); // pro3 Promise { <rejected>: undefined }

调用 resolve / reject 改变 Promise 实例状态时,可传入 1 个参数作为该 Promise 实例的值:

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

const pro2 = new Promise((_, reject) => reject('pro2Reject'));
console.log('pro2', pro2); // pro2 Promise { <rejected>: 'pro2Reject' }

除了调用 resolve / reject,抛出错误也会改变 Promise 实例的状态。抛出错误,Promise 实例的状态从 pending 变为 rejected:

const pro3 = new Promise(() => {
    throw 'errorMsg';
});
console.log('pro3', pro3); // pro3 Promise { <rejected>: 'errorMsg' }

状态只能从 pending 变为 fulfilled / rejected,且状态一旦更新就不会再改变:

const pro = new Promise((resolve, reject) => {
    resolve(); // resolve 写在前面,生效
    throw 'error'; // 后面的 throw 无效
});
console.log('pro', pro); // pro1 Promise { <fulfilled>: undefined }



then & catch & finally

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

方法 then & catch & finally 都挂在 Promise.prototype 上,所以可以通过 Promise 实例 pro 直接调用。

console.log('before executor'); // before executor

new Promise(resolve => {
    resolve();
    console.log('executor'); // executor
}).then(() => {
    console.log('then'); // then
});

console.log('after executor'); // after executor

上例输出的顺序:before executorexecutorafter executorthen


pro.then ( onResolve, onReject )

  1. 回调函数 onResolve:处理状态为 fulfilled 的 Promise 实例。

  2. 回调函数 onReject:处理状态为 rejected 的 Promise 实例 (可选) 。

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

let pro1 = Promise.resolve('Fulfilled');
pro1.then(value => {
    console.log('pro1', value); // pro1 Fulfilled
});

let pro2 = Promise.reject('Rejected');
pro2.then(null, reason => {
    console.log('pro2', reason); // pro2 Rejected
});

onResolve & onReject 会返回一个新的 Promise 实例:

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

let proThen = pro.then(value => {
    console.log('value', value); // value Fulfilled
    return Promise.reject('superman');
});

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

注意:因为 then 是异步操作,所以上例的输出顺序为 proThen Promise {<pending>}value Fulfilled

也因为 then 是异步操作,所以无法同步获取 proThen
如果想获取正确的 proThen,可以将输出语句也写为异步操作:

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

const then1 = pro.then(val1 => {
    console.log('val1', val1); // val1 pro
    return Promise.resolve('then1Resolve');
});
setTimeout(() => {
    console.log('then1', then1); // then1 Promise {<fulfilled>: 'then1Resolve'}
}, 0);

const then2 = pro.then(val2 => {
    console.log('val2', val2); // val2 pro
    return Promise.reject('then2Reject');
});
setTimeout(() => {
    console.log('then2', then2); // then2 Promise {<rejected>: 'then2Reject'}
}, 0);

上例的输出顺序为:
val1 pro - val2 pro - then1 Promise {<fulfilled>: 'then1Resolve'} - then2 Promise {<rejected>: 'then2Reject'}


如果 return 的数据不是 Promise 实例,会自动创建 fulfilled 状态的 Promise 实例,并将 return 的数据设置为 Promise 实例的值
如果没有 return 语句,则该 Promise 实例的值为 undefined

const pro = Promise.resolve('proFulfilled');

const then1 = pro.then(value => {
    console.log('value', value); // value proFulfilled
    return 'superman';
});
setTimeout(() => {
    console.log('then1', then1); // then1 Promise {<rejected>: 'superman'}
}, 0);

const then2 = pro.then(value => {
    console.log('value', value); // value proFulfilled
});
setTimeout(() => {
    console.log('then2', then2); // then2 Promise {<fulfilled>: undefined}
}, 0);

pro.catch ( onReject )

catch(onReject)then(null, onRejected) 的语法糖:

const pro = new Promise((_, reject) => reject('error'));

const proCatch = pro.catch(reason => {
    console.log('reason', reason); // reason error
});

setTimeout(() => {
    console.log('proCatch', proCatch); // proCatch Promise {<fulfilled>: undefined}
}, 0);

pro.finally ( callback )

  • 状态为 fulfilled 和 rejected 的 Promise 实例都会被 finally 的回调函数处理
  • 但是 pending 状态的 Promise 实例会被 finally 的回调函数处理
  • finally 的回调函数不接收参数
const pro1 = Promise.resolve();
pro1.finally(_ => console.log('pro1 resolve')); // pro1 resolve

const pro2 = Promise.reject();
pro2.finally(_ => console.log('pro2 reject')); // pro2 reject

const pro3 = new Promise(() => {});
pro3.finally(_ => console.log('pro3 pending'));
  • finally 方法默认返回新的 Promise 实例,新的 Promise 实例的 [状态] 和 [值] 与调用 finally 的 Promise 实例一致
  • 即使显式重写了新的 return 语句,该 return 语句也不会生效
const pro = Promise.resolve('pro');
console.log('pro', pro); // pro Promise {<fulfilled>: 'pro'}

const fin = pro.finally(_ => {
    console.log('finally'); // finally
    return Promise.resolve('finally'); // return 语句无效
});

setTimeout(() => {
    console.log('fin', fin); // fin Promise {<fulfilled>: 'pro'}
    console.log(fin === pro); // false
}, 0);

需要搞懂

① 改变 Promise 状态和执行回调函数,谁先执行

  • 同步改变状态:先改变状态、后执行回调函数
  • 异步改变状态:先执行回调函数,后改变状态
const pro1 = new Promise(resolve => resolve()); // 同步改变状态
console.log('pro1', pro1); // pro1 Promise {<fulfilled>: undefined}

const pro2 = new Promise(resolve => setTimeout(() => resolve(), 0)); // 异步改变状态
console.log('pro2', pro2); // pro2 Promise {<pending>}

② 多个回调的执行

  • 如果 Promise 实例调用了多个回调函数,回调函数会按顺序执行
const pro = Promise.resolve('data');
pro.then(value => console.log(value + 1)); // data1
pro.then(value => console.log(value + 2)); // data2

③ 链式调用

  • 因为 then / catch / finally 返回的还是一个 Promise 实例,所以可以链式调用
const 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

const 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 语句,所以 val2undefined

  • 在链式调用中,没有按照规则传入回调函数的 then、catch、finally 方法,都会被直接跳过:
const 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 方法, 会被直接跳过
    .then(res3 => console.log('res3', res3)); // res3 string  ——  这里 res3 接收的是前面的合法返回值

④ Promise 异常穿透

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

const 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 方法中

const 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 对象
const 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 链没有继续往下执行



resolve & reject

Promise.resolve ( value )

  • 用于创建 fulfilled 状态的 Promise 实例
  • 参数 value 为 Promise 实例的值;不传参数的话, Promise 实例的值为 undefiend
const pro1 = Promise.resolve(123);
console.log('pro1', pro1); // pro1 Promise {<fulfilled>: 123}

const pro2 = Promise.resolve();
console.log('pro2', pro2); // pro2 Promise {<fulfilled>: undefined}
  • 如果参数 value 是 Promise 实例,则直接返回该实例
const pro1 = new Promise(resolve => resolve('resolve'));
const pro2 = Promise.resolve(pro1);
console.log(pro2); // Promise {<fulfilled>: 'resolve'}
console.log(pro1 === pro2); // true

Promise.reject ( reason )

  • 用于创建 rejected 状态的 Promise 实例
  • 参数 reason 为 Promise 实例的值;不传参的话,Promise 实例的值为 undefiend
const pro1 = Promise.reject('reason');
console.log('pro1', pro1); // pro1 Promise {<rejected>: 'reason'}

const pro2 = Promise.reject();
console.log('pro2', pro2); // pro2 Promise {<rejected>: undefined}
  • Promise.resolve(value) 不同的是:即使参数 reason 是 Promise 实例,也会成为新创建的 Promise 实例的值
const pro1 = new Promise(resolve => resolve('pro'));
const pro2 = Promise.reject(pro1);
console.log('pro2', pro2); // pro2 Promise {<rejected>: Promise}



all & race & allSettled & any

Promise.all ( promiseArr )

  • Promise.all(promiseArr) 接收一个 Promise 对象数组作为参数
  • 该方法返回一个新的 Promise 对象

当所有的 Promise 都 fulfilled 后,按照传入的 Promise 数组的顺序 将每个 Promise 对象的 resolve 值存储在一个新的数组中,并将该数组作为 Promise.all 方法返回的 Promise 对象的 resolve 值结果

const p1 = new Promise(resolve => {
    setTimeout(() => {
        resolve('resolve p1');
    }, 500);
});
const p2 = new Promise(resolve => {
    setTimeout(() => {
        resolve('resolve p2');
    }, 1000);
});
const 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 后输出 )
});

只要有一个 Promise 被 rejected,就会立即返回一个 rejected 的 Promise 对象,其中包含第一个被 rejected 的 Promise 对象的错误信息

const p1 = new Promise(resolve => {
    setTimeout(() => {
        resolve('resolve p1');
    }, 1000);
});
const p2 = new Promise((resolve, reject) => {
    setTimeout(() => {
        reject('reject p2');
    }, 2000);
});
const 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.any ( promiseArr )

  • Promise.any(promiseArr) 接收一个 Promise 对象数组作为参数
  • 该方法返回一个新的 Promise 对象

如果传入的 Promise 数组中至少有一个 Promise 对象变为 resolve 状态,则 Promise.any 的返回值会变成一个 Fulfill 状态的 Promise 对象,并且这个对象的值会等于最先变为 resolve 状态的 Promise 对象的 resolve 值

const p1 = Promise.reject('reject p1');
const p2 = new Promise(resolve =>
    setTimeout(() => resolve('resolve p2'), 1000)
);
const p3 = Promise.resolve('resolve p3');

Promise.any([p1, p2, p3])
    .then(value => console.log(value)) // resolve p3
    .catch(error => console.error(error));

如果传入的 Promise 数组中所有的 Promise 对象都变为 reject 状态,则 Promise.any 的返回值会变成一个 Reject 状态的 Promise 对象,并抛出一个 AggregateError 错误,其中包含了所有 reject 的原因信息

const p1 = Promise.reject('reject p1');
const p2 = new Promise((_, reject) =>
    setTimeout(() => reject('reject p2'), 1000)
);
const p3 = Promise.reject('reject p3');

Promise.any([p1, p2, p3])
    .then(value => console.log(value))
    .catch(error => console.error(error)); // AggregateError: All promises were rejected

Promise.allSettled ( promiseArr )

  • Promise.all(promiseArr) 接收一个 Promise 对象数组作为参数
  • 在所有 Promise 对象都 settle 后返回一个新的 Promise 对象,该对象的 Fulfill 状态的值是一个数组,包含每个 Promise 对象的 settle 状态信息(即不管 resolve 还是 reject 都会被处理)
  • 注意:若有错误抛出,则抛出错误信息,并终止函数
const p1 = new Promise(resolve => {
    setTimeout(() => {
        resolve('resolve p1');
    }, 1000);
});
const p2 = new Promise((resolve, reject) => {
    setTimeout(() => {
        reject('reject p2');
    }, 2000);
});
const p3 = new Promise((resolve, reject) => {
    setTimeout(() => {
        reject('reject p3');
    }, 3000);
});

const 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 ( promiseArr )

  • Promise.race(promiseArr) 接收一个 Promise 对象数组作为参数
  • 该方法返回一个新的 Promise 对象,在 promiseArr 包含的 Promise 对象中有一个对象 settled ( 已解决,即已经 fulfilled / rejected ) 时立即 resolve / reject
  • 注意:抛出异常的 Promise 实例不会被 Promise.race 处理
const p1 = new Promise(resolve => {
    setTimeout(() => {
        resolve('resolve p1');
    }, 500);
});
const p2 = new Promise((_, reject) => {
    setTimeout(() => {
        reject('reject p2');
    }, 1000);
});
const 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.race 来实现一个超时控制,如果某个异步操作在指定时间内没有完成,就抛出超时异常:

function timeoutPromise(promise, timeout) {
    // 返回一个新的 Promise 对象
    return Promise.race([
        promise, // 原始的 Promise 对象
        new Promise((_, reject) => {
            // 将给定的时间转换为毫秒单位
            setTimeout(() => {
                reject(new Error('Operation timed out'));
            }, timeout);
        }),
    ]);
}

const timeout = 5000; // 设置超时时间为 5 秒钟
const promise = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('Operation completed successfully');
    }, 3000);
});

// 调用 timeoutPromise 函数测试
timeoutPromise(promise, timeout)
    .then(result => {
        console.log(result); // 打印输出 "Operation completed successfully"
    })
    .catch(error => {
        console.error(error); // 打印输出 "Error: Operation timed out"
    });

上例中,setTimeout 函数模拟了一个需要等待 3 秒钟才能完成的异步操作。我们设置了超时时间为 5 秒钟,所以 Promise.race 方法会在 3 秒钟后返回原始的 Promise 对象的结果。如果在 5 秒钟内未完成该操作,则会抛出超时错误



案例

demo - fs 配合 Promise 异步读取文件:

const fs = require('fs');

// // ① 异步操作的传统解决方案:使用 [回调函数] 完成异步操作
// fs.readFile("./1.txt", (err1, data1) => {
//     if (err1) return console.log(err1.message);
//     console.log("data1", `${data1}`);

//     fs.readFile("./2.txt", (err2, data2) => {
//         if (err2) return console.log(err2.message);
//         console.log("data2", `${data2}`);

//         fs.readFile("./3.txt", (err3, data3) => {
//             if (err3) return console.log(err3.message);
//             console.log("data3", `${data3}`);
//         });
//     });
// });

// ② 使用 Promise 完成异步操作
new Promise((resolve, reject) => {
    fs.readFile('./1.txt', (err1, data1) => {
        if (err1) reject(err1.message);
        resolve(data1);
    });
})
    .then(val1 => {
        console.log('data1', `${val1}`);
        // 返回异步操作的结果
        return new Promise((resolve, reject) => {
            fs.readFile('./2.txt', (err2, data2) => {
                if (err2) reject(err2.message);
                resolve(data2);
            });
        });
    })
    .then(val2 => {
        console.log('data2', `${val2}`);
        // 返回异步操作的结果
        return new Promise((resolve, reject) => {
            fs.readFile('./3.txt', (err3, data3) => {
                if (err3) reject(err3.message);
                resolve(data3);
            });
        });
    })
    .then(val3 => {
        console.log('data3', `${val3}`);
    })
    .catch(err => {
        console.log(err);
    });
  • 将 fs 封装成 Promise 对象来使用
const fs = require('fs');

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

myReadFile('./1.txt')
    .then(val1 => {
        console.log('data1', `${val1}`);
        // 返回异步操作的结果
        return myReadFile('./2.txt');
    })
    .then(val2 => {
        console.log('data2', `${val2}`);
        // 返回异步操作的结果
        return myReadFile('./3.txt');
    })
    .then(val3 => {
        console.log('data3', `${val3}`);
    })
    .catch(err => {
        console.log(err);
    });

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

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

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

const util = require('util'); // 无需下载,直接引入即可使用
let myReadFile = util.promisify(fs.readFile);
  • 也可以使用 then-fs 依赖包,then-fs 已经将 fs.readFile 方法封装好了

    npm i then-fs、② const thenFs = require("then-fs")

const thenFs = require('then-fs');
let myReadFile = thenFs.readFile;

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

JS.Huang

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

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

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

打赏作者

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

抵扣说明:

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

余额充值