看看 Promise 的多个使用场景

100 阅读4分钟

简述

  • Promise构造函数:

    • Promise(resolve, reject):创建一个新的Promise实例。
  • 实例方法:

    • .then(onFulfilled, onRejected):当Promise状态变为fulfilled时调用onFulfilled函数,当Promise状态变为rejected时调用onRejected函数。
    • .catch(onRejected):当Promise状态变为rejected时调用onRejected函数,相当于.then(null, onRejected)的缩写形式。
    • .finally(onFinally):无论Promise状态最终是fulfilled还是rejected,都会执行onFinally回调函数。
  • 静态方法:

    • Promise.resolve(value):返回一个以给定value解析后的Promise对象。
    • Promise.reject(reason):返回一个带有给定reason拒绝原因的Promise对象。
    • Promise.all(iterable):接收一个可迭代对象,并返回一个Promise,只有当可迭代对象中所有Promise都为fulfilled时才会被解决。
    • Promise.race(iterable):接收一个可迭代对象,并返回一个Promise,只要可迭代对象中的任意一个Promise被解决或拒绝,返回的Promise就会跟随它。
    • Promise.allSettled(iterable):接收一个可迭代对象,并返回一个在所有给定的Promise都已经解决或拒绝后解决的Promise,每个返回的结果都是一个描述状态的对象。
    • Promise.any(iterable):接收一个可迭代对象,并返回一个在可迭代对象中的任何一个Promise被解决时解决的Promise
  • 链式调用方法:

    • .then().then():可以通过链式调用多个.then(),实现多个异步操作按顺序执行。
function fun(params) {
    return new Promise((res) => {
        console.log('已经执行')
        setTimeout(()=>{
            res(1)
        },1000)
    });
}
fun().then(res=>{
    console.log(res)
})

注意的是,Promise只是延迟了.then的回调,而不是延迟函数执行,所以,当你调用fun函数时,函数体已经执行,只不过1s之后才回调.then

场景

优化函数回调

  • 模拟一个回调函数
function getData(params, callback) {
    let urls = {
        user: { id: 1, name: "lisi" },
        data: { age: 12, addr: 0002 },
    };
    // 模拟异步操作
    setTimeout(() => {
        callback(urls[params.url] ? urls[params.url] : null);
    }, 1000);
}

这样的例子其实有很多,比如微信小程序里面很多API都是使用函数回调方式,如果需要调用多次了,就会出现回调地狱现象。

  • 优化前
getData({ url: "user" }, (user) => {
    console.log("用户:", user);
    getData({ url: "data" }, (data) => {
        console.log("详情:", data);
    });
});
  • Promise
// 先使用Promise封装一层
function toData(params) {
    return new Promise((res) => {
        getData(params, (data) => {
            res(data);
        });
    });
}
// 界面使用
async function initPage() {
    try {
        let user = await toData({ url: "user" });
        console.log("用户:", user);
        let data = await toData({ url: "data" });
        console.log("详情:", data);
    } catch (e) {
        console.log("error", e);
    }
}
initPage();

这个简单的例子通过将异步操作封装在 Promise 中,优雅地解决了回调地狱问题,并避免了多层嵌套。这种封装方式不仅使代码更加清晰简洁,而且方便了错误处理。

这里使用了async/await的方式,如果使用Promise.then函数依旧无需嵌套函数,但我觉得这种方式更为优雅。

同时执行多个异步

const promise1 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve("Promise 1 resolved");
    }, 2000);
});
​
const promise2 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve("Promise 2 resolved");
    }, 3000);
});
​
const promise3 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve("Promise 3 resolved");
    }, 1000);
});
​
Promise.all([promise1, promise2, promise3])
    .then((results) => {
    // 所有Promise都已经解决后执行
    console.log(results); // ["Promise 1 resolved", "Promise 2 resolved", "Promise 3 resolved"]
    // 进行下一步处理...
})
    .catch((error) => {
    // 如果任何一个Promise被拒绝,则会进入这里
    console.error(error);
});

需要注意的是,如果传入的 Promise 数组中有任意一个 Promise 被拒绝(rejected),那么 Promise.all 将立即终止并返回拒绝的原因。只有在所有 Promise 都解决(即全部成功解决或至少一个被拒绝)后,才会触发 then 方法。

通过race完成无感刷新

这里我拿web-loading这个插件的源码参考

let feelPromiseResolve: ((value: boolean | PromiseLike<boolean>) => void) | null = nullconst loading = (dom: ElementType, options?: OptionsType) => {
    // Processing Senseless Loading through rece
    const loadingPromise = new Promise<boolean>((res) => {
        // If the time of notFeed exceeds the close time, it is considered as an insensitive load
        $window.setTimeout(() => {
            res(true)
        }, op.notFeel)
    })
    const feelPromise = new Promise<boolean>((res) => {
        feelPromiseResolve = res
    })
    Promise.race([loadingPromise, feelPromise]).then((res) => {
        if (res) webLoading.draw(dom)
        else {
            // Process extended dom
            if (op.type !== LOADING_TYPES.DOM) dom.remove()
        }
        feelPromiseResolve = null
    })
}

这里我们可以看到准备两个PromiseloadingPromise代表的是在notFeel秒内还没调用feelPromiseResolve中的回调就draw绘制,如果notFeel秒内调用feelPromise的回调,那就不会draw绘制,从而达到一个无感刷新效果。

这里为了看更清楚,翻译了下

let feelPromiseResolve = null;
function loading() {
    const loadingPromise = new Promise((res) => {
        // 2s以内的无感刷新缓冲时间
        setTimeout(() => {
            res(true);
        }, 2000);
    });
    const feelPromise = new Promise((res) => {
        feelPromiseResolve = res;
    });
    Promise.race([loadingPromise, feelPromise]).then((res) => {
        if (res) console.log("刷新中");
        else {
            console.log("不刷新");
        }
        feelPromiseResolve = null;
    });
}
loading();
// 模拟1s时数据已经加载完成,所以无需显示刷新动画
setTimeout(() => {
    if (feelPromiseResolve) feelPromiseResolve(false);
}, 1000);