你的Promise真的算数吗?😭

前言

众所周知,JS是一门单线程语言,这就意味着同一时间JS只能处理一个任务。从数据获取,文件读写等等诸如此类的许多操作都要等待资源的响应。这种传统的同步处理方式会阻塞程序的执行,导致资源的浪费和用户的体验感变差。那么我们应该如何解决这个问题呢?

异步处理和同步处理

😈异步处理:异步处理时,程序开始执行一个任务后,不会等待结果的返回,立即执行下一个任务,结果会通过回调函数等方法返回。

😇同步处理:同步处理时,程序开始执行一个任务后,会停止并且等待结果的返回,等接收到结果之后再开始执行下一个任务,依此类推。

setTimeout()

而异步处理通常通过异步函数setTimeout()来实现。接下来让我们先简单了解一下这个方法。

让我们来一起看一看下面的代码,输出的结果到底是什么呢?

ini 复制代码
var a = 1

setTimeout(()=>{
    a = 2
},1000)

console.log(a);

答案为1 ,因为setTimeout()为异步函数,它并不会被立即执行,而是被扔进一个事件队列中等待执行。当所有同步函数执行完之后,才会回来检查这个队列并执行相应的回调函数。 以下是执行这段代码的流程:

  1. var a = 1 :声明变量a = 1
  2. setTimeout(()=>{a = 2},1000):声明变量a = 2,延迟1秒(1000毫秒)。这个任务被扔进了事件队列,并在1秒后执行。
  3. console.log(a):输出变量a的值,也就是1。
  4. 1秒后事件队列中的任务被执行,变量a被赋值为2,但是a已经在1秒前被输出,所以在console.log(a)执行时a依旧为1,即使异步函数setTimeout()将a的值改变了。这就是为什么答案为1的原因。

相信现在你已经理解了setTimeout()方法,那让我们再看一个例子巩固一下:

javascript 复制代码
var data = null

function a(){   // ajax
    setTimeout(function(){
        data = 'DDDAMN!'
        b()
    },1000)
}

function b(){ 
    console.log(data);
}
a()

怎么样?答案是不是一眼就出来了呢?答案为DDDAMN! ,因为 function b() 的作用是输出data , 而 function b() 的调用在function a()里,所以它也进入了事件队列等待被执行,故输出DDDAMN! 而不是null

回调地狱

假设有这么一段代码,有一个函数a,它的执行结果要得到函数b的执行返回的结果,函数b的执行结果要得到函数c的执行返回的结果,函数c的执行结果要得到函数d的执行返回的结果。

scss 复制代码
function a(callbackB,callbackC,callbackD){
    callbackB(callbackC,callbackD)
}

function b(callback,callbackD){
    callback(callbackD)
}

function c(callback){
    callback()
}

function d(){
   ****** 
}
a(b,c,d)

函数a 接受三个参数:callbackB, callbackC, 和 callbackD。在这个例子中,callbackBbcallbackCccallbackDd

a 被调用时,它会执行 callbackB(callbackC, callbackD)。由于 callbackB 实际上是 b ,这相当于调用 b(c, d)

然后,b 函数接收两个参数:callbackcallbackD。在这个调用中,callbackccallbackDd 。当 b 被调用时,它会执行 callback(callbackD)。由于 callback 实际上是 c ,这相当于调用 c(d)

最终,c 函数被调用,它只接受一个参数 callback。在这个调用中,callback 就是 d 。 当 c 被调用时,它会执行 callback()。由于 callback 实际上是 d ,这相当于直接调用 d()

这种情况我们称之为回调地狱。因为嵌套太深,一旦代码出现问题,将会很难将之排查出来,维护难度太大。就好比一百个串联的灯泡💡,突然全部都不亮了,你就得一个一个去排查到底是哪个灯泡💡出现了问题,简直让人红温。

Promise

Promise是JS中用于处理异步操作的一种模式,它可以更优雅地来处理异步代码,相比传统的回调函数,Promise可以更好地控制异步流程,避免回调地狱Promise有三种状态:pending(进行中) ,fulfilled(已完成) ,rejected(已拒绝)Promise会接受一个执行器(executor)函数,该函数接受两个参数,分别为resolvereject 。接收后Promise就会变为相应的状态。

下面让我们来看一个例子:

javascript 复制代码
function a(){    // pending  fulfilled  rejected
    return new Promise((resolve,reject)=>{  // {status: pending  -> fulfilled}
    setTimeout(()=>{
        console.log('我要上A');
        resolve()  // {status: fulfilled}
    },2000)
    
    })
}

function b(){
    return new Promise((resolve,reject)=>{
        setTimeout(()=>{
        console.log('我要上S');
        resolve()  // {status: fulfilled}
        })      
    },1000)
    }


function c(){
    console.log('我要上金S');
}

// a().then(()=>{
//     b().then(()=>{
//         c()  
//     })        
// })

a().then(()=>{
    return b()
}).then(()=>{
    return c()
})       

答案如下:

按照前面讲的知识,两个setTimeout()设置的时间分别为1秒和2秒。为什么输出的结果不是

这里就要提到.then()方法了,他是Promise原型上的一个函数,.then()函数会在x这个promise实例对象状态变更为resolved之后才执行内部逻辑,由此借助这个机制可以将异步捋成同步。

上述代码的执行流程是:

  1. a() 开始执行,它返回一个新的 Promise。Promise 的执行器函数会在所有的同步代码执行完毕后执行,然后调用 resolve() ,将Promise的状态从Pending 改变为Fulfilled

  2. a().then(() => {...}) 被调用。由于 a() 返回的Promise还没有完成,.then() 方法会返回一个新的 Promise 并等待 a()Promise完成。当 a()Promise完成时.then()中的回调函数会被执行。

  3. .then(() =>{...}) 被调用,由于 b() 的调用,b() 同样返回一个 Promise。因此,这个 .then()的回调函数会等待 b()Promise完成。当 b()Promise完成后,链式调用的下一个.then()被触发。然后返回 c() 的调用输出结果。

resolve(x) 这个x会指定交给then()中的回调函数。

javascript 复制代码
var data = null

function a(){
    return new Promise(function(resolve, reject) {
        setTimeout(function(){
        console.log('a is done');
        data = 'M3'
        resolve('DDAMN')
        },1000)
    })
}

function b(){
    console.log(data);
}

a().then((res)=>{
    b()
    console.log(res);
})

结果如下:

reject(x) 这个x会指定交给catch()中的回调函数,catch()是专门来捕获程序中的错误的。 当然也可以通过 reject() 改变状态:

javascript 复制代码
var data = null
function a(){
    return new Promise(function(resolve, reject) {
        setTimeout(function(){
        console.log('a is done');
        data = 'M3'
        reject('DAMN')
        },1000)
    })
}

function b(){
    console.log(data);
}

a.catch((err)=>{
    console.log(err);
});

结果如下:

那么让我们再用Promise解决前面的回调地狱吧:

scss 复制代码
function a() {
    return new Promise((resolve) => {
        b(resolve, c, d);
    });
}

function b(callback, callbackC, callbackD) {
    return new Promise((resolve) => {
        callbackC(resolve, callbackD);
    });
}

function c(callback, callbackD) {
    return new Promise((resolve) => {
        callbackD();
        resolve(); 
    });
}

function d() {
    console.log('D is done');
}

// 使用 Promise 的链式调用
a().then(() => b())
    .then(() => c())
    .then(() => d());

是不是学会了呢?

希望这篇文章能帮助你更好的理解Promise,并加以运用到实战当中。🥳

相关推荐
恋猫de小郭27 分钟前
Flutter Zero 是什么?它的出现有什么意义?为什么你需要了解下?
android·前端·flutter
崔庆才丨静觅7 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60618 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了8 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅8 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅8 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅9 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment9 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅9 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端