JS-Promise详解

本文详细介绍了JavaScript中的Promise,旨在解决回调地狱问题。通过实例解析Promise的构造、状态改变、公共方法如then(), catch()和finally(),并探讨Promise.all(), any(), race()的使用场景。此外,文章还介绍了async/await作为Promise的语法糖,以及在异步和同步机制中的应用。" 79428035,5686716,Ubuntu 17.10 安装 Docker CE 指南,"['Docker', 'Ubuntu', 'Linux', '软件安装', '系统管理']

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Promise

Promise:ES6新增的内置类(构造函数),用来规划异步编程代码,解决回调地狱问题

 let p1 = new Promise([executor])
     + [executor]必须是一个函数,而且new Promise的时候会立即把其执行 “同步”
     + p1是其创建出来的实例
       私有属性
       + [[PromiseState]]:"pending"、"fulfilled"、"rejected"  实例的状态
       + [[PromiseResult]]:undefined  实例的值「成功的结果或失败的原因」
       公共方法:Promise.prototype
       + then
       + catch
       + finally
       + ...
     + p1.then([onfulfilled],[onrejected])
       + [onfulfilled]/[onrejected]都是函数
       + 实例状态是成功fulfilled的时候,会把[onfulfilled]执行,并且把实例的值作为成功的结果传递给他
       + 实例状态是失败rejected的时候,会把[onrejected]执行,把实例值作为失败原因传递给他 

回调地狱

以jQuery的ajax请求为例,在回调函数里面嵌套回调函数称为回调地狱

 $.get('./data.json', function cb1(data) {
    $.get('./data2.json', function cb2(data2) {
      $.get('./data3.json', function cb3(data3) {
        $.get('./data4.json', function cb4(data4) {
           console.log(data, data2, data3, data4)
        })
      })
    })
  }) 

如何修改实例的状态和值

 //基于这种方式创建实例 
      let p=new Promise((resolve,reject)=>{ 
         ...
      })
      + 如果executor函数执行报错,则把实例的状态修改为rejected,值是报错原因「不会抛异常」
      + resolve/reject也是函数
        resolve('OK') -> 把实例p的状态修改为fulfilled,值(成功结果)是'OK'
        reject('NO') -> 把实例p的状态修改为rejected,值(失败原因)是'NO'
      + 一但状态被修改为fulfilled或rejected,后期就不会再更改状态值了 

Promise的公共方法

then()

 每一次执行THEN方法,都会返回一个“全新的promise实例”
     let p2 = p1.then(onfulfilled,onrejected);
     不论是onfulfilled还是onrejected执行(由p1状态来决定),方法的执行决定了p2的状态和值
     + 首先看方法执行是否报错,如果报错了,则p2是失败态(rejected),值是报错原因
     + 如果执行不报错,再看方法的返回值
     + 如果返回的是“新的promise实例 -> @NP”,则@NP的状态和值直接决定了p2的状态和值
     + 如果返回的不是新实例,则p2状态是成功(fulfilled),值是函数的返回值
     
     执行Promise.resolve/reject/all/any/race...等静态私有方法,也会创建新的promise实例
     + Promise.resolve(10) 创建一个状态是成功fulfilled,值是10的实例
     + Promise.reject(0) 创建一个状态是失败rejected,值是0的实例 
let p1 = new Promise(function (res, rej) {
    res(888, 1, 2, 3, 4, 5, 6);//将实例状态变为成功态
    // res执行的时候可以让then的第一个回调函数执行
    // then的第一个回调函数 只有当实例的状态由pending变成fulfilled的时候才会触发
    // res 执行的时候的第一个“参数”会传给then的“第一个回调函数”,res的是他参数没用
  })
  p1.then(function then的回调函数1(data) {
    console.log(1111, data);//实例为成功态时执行这个函数 值是888
  }, function then的回调函数2(err) {
    console.log('err', err);//实例为失败态执行then的第二个函数  值是rej执行传递的第一个参数
  }) 

catch()

THEN链的顺延/穿透机制

在项目中:then中一般只传递onfulfilled「成功干什么」,最后加一个catch;这样不论中间哪个环节创建了失败的实例,都会穿透至最后一个catch;catch不加,出现失败的实例,控制台报“红”,但是不影响其他代码执行!!

.then(onfulfilled,onrejected),两个方法可以传可以不传,如果不传,则顺延至下一个THEN中,相同状态要执行的方法
中去处理!!

+ 原理:我们不设置对应的方法,PROMISE内部会默认加一个对应的方法,可以让其实现状态的顺延/穿透
 
 p.catch(onrejected) 等价于 p.then(null,onrejected) 
let p1 = new Promis((res,rej){
rej(error);
})

//then链 连续的then:then执行会返回新的Promise实例(如果代码执行没有返回Promise实例的话) 所以可以调用then
p1.then((data)=>{
console.log(data,'第一个成功态');
})
.then((value)=>{
console.log(value,'第二个成功态');
})
.then((value1)=>{
console.log(value1,'第三个成功态')
})
.catch((error)=>{
console.log(error,'失败态');//此处执行catch,then链中都没有写第二个函数,所以会顺延至catch
}) 

finally()

Promise.resolve(10)
    .then(value => {
        console.log('成功', value);
        return value * 10;
    })
    .then(value => {
        console.log('成功', value);
        return Promise.reject(value * 10);
    })
    .then(value => {
        console.log('成功', value);
        return value * 10;
    })
    .catch(reason => {
        console.log('失败', reason);
    })
    .finally(()=>{
    console.log('最后')
        //不论成功还是失败,最后都有要执行finally中的方法「一般很少使用」
    }); 

关于Promise.all/any/race三个方法

 let p = Promise.all/any/race([promises]);
      + promises是包含零到多个promise实例的集合,一般是数组!如果集合中的某一项不是promise实例,则默认变为状态为成功,值是本身的promise实例!!
      + all:集合中的“每个实例都成功”,最后结果p才成功,值是按照集合顺序,依次存储每个实例成功结果的数组;其中只要有一个实例失败,则p就是失败的,值是本次失败的原因,后面的操作不再处理!!
      + any:只要有一个成功,最后p就是成功的,值是本次成功的结果;都失败,最后p才是失败!{兼容性不好}
      + race:集合中谁最先知道结果,则以谁的为主! 

AJAX的串行和并行:在平时的项目中发送ajax请求都是"异步编程"

并行:多个请求同时发送,谁先回来就先处理谁,经常用于多个请求之间没有依赖的时候偶尔我们需要监测,多个请求都成功,再去做什么事==>Promise.all 串行:多个请求之间存在依赖,无法同时发送请求,上一个请求发送成功,我们才能发送下一个请求(往往是下一个请求需要用到这一个请求来的结果,才需要这么做)

AJAX并行
$.ajax({
    url: '/api1',
    success(result) {
        console.log(`第一个请求成功:`, result);
    }
});
$.ajax({
    url: '/api2',
    success(result) {
        console.log(`第二个请求成功:`, result);
    }
});
$.ajax({
    url: '/api3',
    success(result) {
        console.log(`第三个请求成功:`, result);
    }
}); 
JQ中的AJAX串行 -> 回调地狱
$.ajax({
    url: '/api1',
    success(result) {
        console.log(`第一个请求成功:`, result);
        $.ajax({
            url: '/api2',
            success(result) {
                console.log(`第二个请求成功:`, result);
                $.ajax({
                    url: '/api3',
                    success(result) {
                        console.log(`第三个请求成功:`, result);
                    }
                });
            }
        });
    }
}); 

基于Promise解决以上回调地狱的问题

axios.get('/api1')
    .then(value => {
        console.log(`第一个请求成功:`, value);
        return axios.get('/api2');
    })
    .then(value => {
        console.log(`第二个请求成功:`, value);
        return axios.get('/api3');
    })
    .then(value => {
        console.log(`第三个请求成功:`, value);
    });
//上一个then执行完后返回一个新实例,才会执行下一个then 

async/await

async/await:是Promise+Generato的"语法糖"

async是对函数的修饰 ==>async function xxx(){....}
 + 让函数的返回结果自动变为一个Promise'实例
   - 如果函数执行报错,就返回一个rejected状态的实例  值是报错原因
   - 如果函数没有报错,再看返回值
     - 返回值是新的Promise实例,则以函数返回的实例为准
     - 其他情况则返回一个fulfilled状态的实例  值是函数的返回值
 + 可以在函数中使用await
await可以监听Promise实例的状态,从而决定去做什么事 -->let xxx= await [Promise实例];
 + 必须出现在一个函数中,并且是经过async修饰过的函数
 + await后面需要跟一个Promise实例[如果不是Promise实例,浏览器会自动将他变为Promise实例]
     await 14; => await Promise.resolve(14);
 + await会等待后面的实例状态为“成功”时才会把“当前上下文中”await下面的代码执行  xxx就是实例为成功返回的结果
 + 如果await后面的实例状态为失败,则下面代码不会执行(控制台会“爆红”,但是不影响代码执行)
 + 我们基于try/catch实现对await后面的实例为失败态的处理,避免出现爆红 

模拟从服务器获取数据

// 模拟从服务器获取数据
const query = () => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            // resolve({ code: 0, data: [10, 20, 30] });
            reject('请求失败');
        }, 2000);
    });
};

(async function() {
    try {
        let result = await query();
        //在不知道请求结果之前,当前上下文await下面的代码是不会执行;只有后面实例状态是成功,下面代码才执行;
        //如果实例状态是失败,下面代码也不执行「因为我们没有做失败情况的处理,所以控制台抛红」
        console.log('请求成功:', result);
    } catch (reason) {

        console.log('请求失败:', reason); // 实例状态是失败
    }

    query()
        .then(result => {
            console.log('请求成功:', result);
        })
        .catch(reason => {
            console.log('请求失败:', reason);//失败状态穿透至catch
        });

})(); 

async/await练习

const sleep = (interval = 1000) => {
    return new Promise(resolve => {
        setTimeout(() => {
            resolve();
        }, interval);
    });
};

const handler = async () => {
    await sleep(2000);
    console.log('第一个定时器触发执行');

    await sleep(1000);
    console.log('第二个定时器触发执行');

    await sleep(3000);
    console.log('第三个定时器触发执行');
};

handler().then(() => {
    console.log('当函数内部所有await都处理完,而且对应的实例都是fulfilled,才会让handler执行的返回值是成功的promise实例!');
}).catch(() => {
    console.log('代码执行报错,或者其中某个await后面的实例是失败的,则直接让handler返回值也是失败的');
});

sleep(2000)
    .then(() => {
        console.log('第一个定时器触发执行');
        return sleep(1000);
    })
    .then(() => {
        console.log('第二个定时器触发执行');
        return sleep(3000);
    })
    .then(() => {
        console.log('第三个定时器触发执行');
    }); 

Promise & async/await 中的异步和同步机制

异步微任务 & 异步宏任务

浏览器是多线程,但是它只分配一个“JS引擎线程”用来渲染和解析JS代码,所以JS是单线程的!!
   + JS中大部分代码都是“同步编程”,例如:循环...
     + 千万不要写死循环,一但遇到死循环,则JS引擎线程会一直被占用,其它事情都做不了了
     + 遇到程序抛出异常,后面的代码也不会再执行了
       + 我们可以基于 try/catch 进行异常捕获,这样不会影响后续代码的执行
     + ...
JS中也存在“异步编程”:依托于浏览器多线程,再基于EventLoop事件循环机制处理的
     + 异步宏任务 macrotask
       + 定时器 setTimeout/setInterval
       + 事件绑定 
       + ajax/fetch
       + ...
     + 异步微任务 microtask
       + requestAnimationFrame 
       + Promise.then/catch/finally
       + async await
       + queueMicrotask 基于这个方法可以创建一个异步微任务
       + IntersectionObserver
       + ...
  异步微任务优先于异步宏任务执行,即使宏任务比微任务先进入EventQueue中等待 

案例练习

目前已知实例状态案例

/* 
new Promise(resolve => {
    // EXECUTOR函数会被立即执行「同步」:这里一般是管理异步编程代码,当异步结束,基于resolve/reject执行,控制实例状态的成功或者失败...
    console.log(1);    先打印1
});
console.log(2);       再打印2
*/
let p1 = new Promise(resolve => {
    /!* 
     resolve/reject执行:
       + 立即修改实例的状态和值「同步」 
    *!/
    resolve(100);
});
console.log(p1); //  =>  实例状态为fulfilled & 值为100
/* p1.then(onfulfilled,onrejected)
   + 如果此时已知p1实例的状态(不是pending),会根据状态去执行onfulfilled/onrejected;
     + 但并不会立即执行,而是创建一个“异步的微任务”
     + 进入到WebAPI中去监听,但是此时已经知道状态是成功的,则直接在挪至EventQueue中排队
     + 当同步代码执行完,主线程空闲下来了,再去EventQueue中查找   */
     
p1.then(value => {
    console.log('成功:', value);
}, reason => {
    console.log('失败:', reason);
});
console.log(111); 
目前位未知实例状态案例

let p1 = new Promise(resolve => {
    setTimeout(() => {
        /* 
        立即修改实例的状态和值「同步」
        通知之前在集合中存储的方法执行「异步微任务」
          + 把存储的方法扔到WebAPI中去监听,但是发现实例状态已经是成功了,则挪至到EventQueue中排队等着
          + 把此上下文中剩下的代码执行完,再去获取这个异步任务执行
        */
        resolve(100);
        console.log(222, p1);
    }, 2000);
});
 console.log(p1); // 目前实例状态为pending & 值为undefined
/*
 执行THEN的时候,此时不知道p1实例的状态
   + 把 onfulfilled & onrejected 存储到PROMISE内部的集合中
 */
p1.then(value => {
    console.log('成功:', value);
});
console.log(111); 
遇到await案例
/* 
 遇到await
   + 把await后面的东西进行处理「同步」,获取一个promise实例
   + 然后把当前上下文中,await下面的代码,设置为“异步的微任务”
     + 进入WebAPI监听,当await后面的实例是成功的,再挪至EventQueue中排队等着
*/
 async function async1() {
    console.log('async1 start');
    await async2();
    console.log('async1 end');微1
}
async function async2() {
    console.log('async2');
}
console.log('script start');
setTimeout(function () {宏1
    console.log('setTimeout');
}, 0)
async1();
new Promise(function (resolve) {
    console.log('promise1');
    resolve();
}).then(function () {微2
    console.log('promise2');
});
console.log('script end');

打印先后顺序为:1.script start   2.async1 start 3.async2 4.promise1 5.script end 6.async1 end  
7.promise2  8.setTimeout 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值