你的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,并加以运用到实战当中。🥳

相关推荐
陈随易1 小时前
农村程序员-关于小孩教育的思考
前端·后端·程序员
云深时现月1 小时前
jenkins使用cli发行uni-app到h5
前端·uni-app·jenkins
昨天今天明天好多天1 小时前
【Node.js]
前端·node.js
亿牛云爬虫专家1 小时前
Puppeteer教程:使用CSS选择器点击和爬取动态数据
javascript·css·爬虫·爬虫代理·puppeteer·代理ip
2401_857610031 小时前
深入探索React合成事件(SyntheticEvent):跨浏览器的事件处理利器
前端·javascript·react.js
刘艳兵的学习博客1 小时前
刘艳兵-DBA033-如下那种应用场景符合Oracle ROWID存储规则?
服务器·数据库·oracle·面试·刘艳兵
雾散声声慢1 小时前
前端开发中怎么把链接转为二维码并展示?
前端
熊的猫1 小时前
DOM 规范 — MutationObserver 接口
前端·javascript·chrome·webpack·前端框架·node.js·ecmascript
天农学子1 小时前
Easyui ComboBox 数据加载完成之后过滤数据
前端·javascript·easyui
mez_Blog1 小时前
Vue之插槽(slot)
前端·javascript·vue.js·前端框架·插槽