Promise基础知识以及语法分析

670 阅读7分钟

Promise:承诺者模式;ES6新增的内置类,基于Promise可以有效管理“异步编程”,避免回调地狱

ajax"串行":上一个请求成功,才可以发送下一个请求

ajax"并行":多个请求可以同时发送

需求:三个ajax请求,我们要实现ajax的“串行”【上一个请求成功,才可以发送下一个请求;同理还有一个ajax“并行”:多个请求可以同时发送(偶尔需要等所有并行请求成功后,统一做啥事)】

1.基于ajax的同步操作,实现出ajax“串行”;但是真实项目中是不允许使用ajax同步处理(因为请求没成功之前,所有的其他事情都被阻碍,也无法实现ajax的并行);

/* let data =null;
$.ajax({
    url:'./api/data1.json',
    async:false,
    success(result){
        data = result;
    }
});
console.log(`第一个请求成功,结果是:${data}`);

$.ajax({
    url:'./api/data2.json',
    async:false,
    success(result){
        data = result;
    }
});
console.log(`第二个请求成功,结果是:${data}`);
  1. JQ中的ajax是基于回调函数的方式管理的【请求成功,会触发success回调函数执行,result就是本次请求获取的结果;如果想实现ajax串行,需要吧下一个请求发送,放在上一个请求成功的回调函数中处理,如果有多个串行请求,就会一层层的嵌套==>‘回调地狱’【代码看起来很乱、不方便管理】;
  • 痛点:在传统方案中,基于回调函数的方式管理异步编程的代码,总是要在异步任务可执行的时候,在他的回调函数中处理一些事情,这样很容易就产生回调地狱!
$.ajax({
    url: './api/data1.json',
    // async:false,
    success(result) {
        console.log(`第一个请求成功,结果是:${result}`);
        $.ajax({
            url: './api/data2.json',
            // async:false,
            success(result) {
                console.log(`第二个请求成功,结果是:${result}`);
                $.ajax({
                    url: './api/data3.json',
                    success(result) {
                        console.log(`第三个请求成功,结果是:${result}`);
                    }
                });
            }
        });
    }
});
const qurey1= ()=>{
    return new Promise(resolve=>{
        $.ajax({
            url:'./api/data1.json',
            success(result){
                resolve(result);
            }
        });
    });
};
const qurey2= ()=>{
    return new Promise(resolve=>{
        $.ajax({
            url:'./api/data2.json',
            success(result){
                resolve(result);
            }
        });
    });
};
const qurey3= ()=>{
    return new Promise(resolve=>{
        $.ajax({
            url:'./api/data3.json',
            success(result){
                resolve(result);
            }
        });
    });
};


 //解决方案Promise
 qurey1().then(result=>{
    console.log(`第一个请求成功,结果是:${result}`);
    return qurey2();
}).then(result=>{
    console.log(`第二个请求成功,结果是:${result}`);
    return qurey3();
}).then(result=>{
    console.log(`第三个请求成功,结果是:${result}`);
});
 
//解决方案: async / await「Promise + generator的语法糖」
(async function(){
    let result = await qurey1();
    console.log(`第一个请求成功,结果是:${result}`);
    result = await qurey2();
    console.log(`第二个请求成功,结果是:${result}`);
    result = await qurey3();
    console.log(`第三个请求成功,结果是:${result}`);
})();

Primose语法

let p = new Promise([exector]);

  • p 是他的实例
  • [exector]是一个函数,传递的不是函数就会报错;
  • p.proto===Promise.prototype

私有属性:

[[PromiseState]]: "pending" 存放状态的

[[PromiseResult]]: undefined(存储的是成功的结果/失败的原因)

  • Promise实例状态:pending准备状态 、 fulfilled/resolved成功状态 、 rejected失败状态
  • 最初的状态是pending,后期基于某些操作可以把状态改变为fulfilled/rejected(一旦状态变为成功或者失败就再也不能改变为其他状态),而[[PromiseResult]]: undefined(存储的是成功的结果/失败的原因)

公有属性:

then, catch, finally, Symbol(Symbol. toStringTag): "Promise'

疑惑一:状态改完就什么用?

  1. 基于then可以存放俩个函数 p.then(onfulfilled,onrejected)
  2. 当我们把状态修改为fulfilled成功态,则会把onfulfilled这个函数执行,相反,我们把状态修改为rejected失败态,则会把onrejected这个函数执行...
  3. 并且会把[[PromiseResult]]作为实参值,传递给onfulfilled/onrejected

疑惑二:怎么修改状态

  1. new Promise时,会立即把传进来的exector函数执行,并且会给exector传递俩实参进来,我们会用俩个形参变量接收
  • resolve,reject,并且值是俩个函数
  1. 当我们执行resolve,Promise实例状态改变为fulfilled成功,传递的值就是成功的结果,赋值给[[PromiseResult]];同理,只要reject执行,Promise实例状态改变为rejected失败,传递的值就是失败的原因,赋值给[[PromiseResult]]5
  2. 如果exector执行报错,则实例的状态也是rejected失败,失败原因就是错误信息
 let p = new Promise(function exector(resolve,reject) {

});
p.then(function onfulfilled(result){

},function onrejected(reason){

}); 


 new Promise((resolve,reject)=>{
    //在这里一般管理异步编程的代码
     $.ajax({
        url:'./api/data1.json',
        success(result){
            resolve(result); 
        },error(reason){
            reject(reason);
        }
    }); 


    setTimeout(()=>{
        let ran = Math.random();
        if(ran>0.5) resolve('ok');
        reject('no');
    })
}).then(result=>{
    console.log('请求成功',result);
},reason=>{
    console.log('请求失败',reason);
}); 

promise实例.then的用途

我们可以基于"promise实例.then"存放多个onfulfilled/onrejected方法,状态变为成功或者失败,存放每个对应的方法都会被执行

let p1 = new Promise((resolve, reject) => {
    resolve(100);
});
p1.then(result => {
    console.log(`成功:${result}`);
}, reason => {
    console.log(`失败:${reason}`);
});
p1.then(result => {
    console.log(`成功:${result}`);
}, reason => {
    console.log(`失败:${reason}`);
}); 

new promise 产生的实例,他的状态是成功还是失败,取决于“(resolve, reject”执行 或者executor执行是否报错)

let p1 = new Promise((resolve, reject) => {
    reject(100);
});

每一次执行then方法,不仅存放了onfulfilled/onrejected方法,而且还会返回一个“全新的promise实例”

新实例p2的状态和值,由谁来决定?

不论onfulfilled/onrejected执行的是哪一个,我们只看执行是否报错;如果报错则p2的状态是失败(reject),值是报错原因;如果不报错则p2的状态是成功fulfilled,值是函数的返回值!

let p2 = p1.then(result => {
    console.log(`成功:${result}`);
    return 1000;
}, reason => {
    console.log(`失败:${reason}`);
    return -1000;
});
let p3 = p2.then(result => {
    console.log(`成功:${result}`);
    throw new Error('xxxx')
}, reason => {
    console.log(`失败:${reason}`);
});
p3.then(result => {
    console.log(`成功:${result}`);
}, reason => {
    console.log(`失败:${reason}`); //失败:Error: xxxx
});

特殊情况:我们之前说不论onfulfilled/onrejected执行,只要不报错,则实例p2的状态就是成功,只要报错就是失败.......但是有个特殊情况:“执行不报错,但是返回的值是一个全新的promise实例,这样返回值的这个promise实例是成功还是失败,直接决定p2是成功还是失败”

Promise中的then链机制:

  • 因为每一次.then都会返回一个新的promise实例,所以我们就可以持续 .then下去了
  • 而且因为实例的诞生的方式不同,所以判断标准不同

当promise.then(onfulfilled.onrejected)

  • 情况一:我此时已经知道promise是成功还是失败状态de

我们应该去执行onfulfilled或者onrejected,但是不是立即执行,它是一个异步的微任务; 首先把执行对应的方法这个事情放在WebAPI中监听,但是因为此时已经知道状态了,对应的方法肯定执行,所以紧接着把他挪至到EventQueue中【任务等待执行队列】等待执行

  • 情况二:此时promise还是pending状态

我们把onfulfilled或onrejected先存储起来,只有当后面,我们把实例的状态修改为成功/失败的时候,再取出之前存储的方法,把其执行【而且此时再执行,还是个异步微任务】 还是要经历:WebAPI->EventQueue

then链具有穿透性(顺延):

  • 正常情况下.tnen会传递俩个函数onfulfilled/onrejected,但是有些时候我们是不传递其中的某个函数的,例如:.then(null,onrejected) 或则 .then(onfulfilled) ,这种情况,我们要采取顺延策略;找到下一个then中对应的状态的函数执行
  • 几个小例题来巩固then链的穿透性:
Promise.reject(0).then(result => {
    console.log(`成功:${result}`);
    return 1;
}).then(result => {
    console.log(`成功:${result}`);
    return 2;
}).then(result => {
    console.log(`成功:${result}`);
    return 3;
}, reason => {
    console.log(`失败:${reason}`); //失败:0
});

 Promise.resolve(100).then(result => {
    console.log(`成功:${result}`); //成功:100
    throw '我失败了';
}).then(result => {
    console.log(`成功:${result}`);
    return 2;
}).then(result => {
    console.log(`成功:${result}`);
    return 3;
}, reason => {
    console.log(`失败:${reason}`); //失败:我失败了
});

Promise.resolve(100).then(result => {
    console.log(`成功:${result}`); //成功:100
    return 1;
}).then(result => {
    console.log(`成功:${result}`); //成功:1
    return Promise.reject('NO');
}).then(null, reason => {
    console.log(`失败:${reason}`); //失败:NO
});

1.png

catch

catch的用途

  • .catch(onrejected)===>.then(null,onrejected)
  • 真实项目中,我们经常:then中只传递onfulfilled,处理状态是成功的事情;在then链的末尾设置一个catch,处理失败的事情(依托于then链的穿透机制,无论最开始还是哪个then中,出现了让状态为失败的情况,都会顺延到最末尾的catch部分进行处理)

JS中的异常捕获

try中执行代码,如果执行不报错,则不走catch,如果执行报错,直接进入到catch中,而且会用一个形参来接收报错的原因,但是不论执行是否报错,都会走最后的finally

try/catch的作用:捕获异常信息,不影响下面的代码执行;还可以收集错误信息,发送给后台做统计

try{
    //放异常信息
}catch(err){
    console.log(err);
};

Promise.all([promise1,promise2,promise3])并行管控

并行中的综合处理:

一起发送多个请求(处理多个异步),但是需要等待所有异步都成功,我们在做一些事

  • 执行Promise.all返回一个新的promise实例@p
  • 并且传递一个数组,数组中包含N多个其他的promise实例
  • 如果数组中的每一个promise实例最后都是成功的,则@p也将会是成功的,它的值也是一个数组,按照顺序依次存储各个promise实例的结果;但凡数组中的某个promise实例是失败的,则@P也是失败的,值是当前这个实例失败的原因!
  • 如果数组中的某一项并不是promise实例(只要不是promise实例就会按默认机制来),则浏览器也会默认变成一个状态是成功的,promise实例,值就是当前项本身;
  • 返回结果顺序和最开始是一致的,不会考虑谁先成功

异步的“串行”

第一个异步成功后才能发送第二个,第二个成功才能发送第三个....多个异步之间一般是有依赖的

const fn1 = ()=>{
    return new Promise(resolve=>{
        setTimeout(()=>{
            resolve(1);
        },1000);
    });
};
const fn2 = ()=>{
    return new Promise(resolve=>{
        setTimeout(()=>{
            resolve(2);
        },2000);
    });
};
const fn3 = ()=>{
    return new Promise(resolve=>{
        setTimeout(()=>{
            resolve(3);
        },3000);
    });
};

fn1().then(reslut=>{
    console.log(`第一个成功${result}`);
    return fn2();
}).then(reslut=>{
    console.log(`第二个成功${result}`);
    return fn3();
}).then(reslut=>{
    console.log(`第三个成功${result}`);
}).catch(reason=>{
    console.log(`只要其中一个失败,直接穿透(顺延)到这里,其余剩下的请求就不发送了`);
}) */

// 真实项目中,想实现异步的串行,我们一般使用async
(async function(){
    let result = await fn1();
    console.log(`第一个成功${result}`);
     result = await fn2();
    console.log(`第二个成功${result}`);
     result = await fn3();
    console.log(`第三个成功${result}`);
})();

async函数+await

  • promise状态是失败,如果不用catch(或者onrejected)处理,控制台会抛出异常:Uncaught(in promise)xxx,但是此异常不会阻碍下面代码的执行

async修饰符:修饰一个函数,让函数的返回值成为一个promise实例

  • 如果函数自己本身就返回一个promise实例,则以自己返回的为主
  • 如果函数本身没有返回promise,则会把返回值变成一个promise实例;状态->成功 值->返回值
  • 如果函数执行报错,则返回的实例 状态->失败 值->报错原因

async最主要的作用:如果想在函数中使用await,则当前函数必须基于async修饰

await的作用

await:等待,我们一般在其后面放promise实例,她会等待实例状态为成功,再去执行当前上下文中,await下面的代码,【如果管控的是一个异步编程,其实他是在等待异步成功,在执行下面的代码,类似于异步改为同步效果】; 如果不是promise实例,浏览器默认会把其转换为“状态为成功,值就是这个值”的实例;

const fn1 = ()=>{
    return new Promise(resolve=>{
        setTimeout(()=>{
            resolve(1);
        },1000);
    });
};
(async function(){
    let result = await fn1();
    console.log(result); //下面代码可以执行,说明await后面的promise实例,它的状态已经是成功了,await的返回值就是当前promise实例的值
})();
  • 如果await后面的promise实例状态为失败的,则下面代码永远不会执行

同步异步执行

同步代码执行,遇到一个异步任务

  1. 先把其放到webAPI进行监听
  2. 当前异步任务监听到可以执行,则再把其放到在EventQueue中,排队等待执行

同步任务执行结束,主线程空闲

  1. 去EventQueue中找可执行的微任务,如果微任务都执行完了,再去找可执行的宏任务【队列:优先级队列&先进先出】
  2. 取到的任务都放在Stark中交给主线程去执行

这就是事件的循环机制EventLoop

await中的异步:当前上下文,await下面的代码执行是异步微任务

  • 情况一:await后面的promise实例我们已经是成功的

先把微任务放置在WebAPI中,但是知道是可以执行的,则直接再挪至到EventQueue中等待

  • 情况二:await后面的promise实例还是pending状态

此时我们把微任务放置在WebAPI中监听,等到后期promise实例是成功态后,再把它挪到EventQueue中等待执行即可

练习题【来巩固一下promise和同步异步执行吧】

例题1
console.log(1);
setTimeout(() => {
    console.log(2);
});
console.log(3);
let p1 = new Promise(resolve => {
    console.log(4);
    resolve('A');
    console.log(5);
});
console.log(6);
p1.then(result => {
    console.log(result);
});
console.log(7);
let p2 = new Promise(resolve => {
    setTimeout(() => {
        resolve('B');
        console.log(10);
    });
});
console.log(8);
p2.then(result => {
    console.log(result);
});
console.log(9);

微信图片_20210525174449.png

例题2
 let body = document.body;
body.addEventListener('click', function () {
    Promise.resolve().then(() => {
        console.log(1);
    });
    console.log(2);
});
body.addEventListener('click', function () {
    Promise.resolve().then(() => {
        console.log(3);
    });
    console.log(4);
});

2.png

例题3
async function async1() {
    console.log('async1 start');
    await async2();
    console.log('async1 end');
}
async function async2() {
    console.log('async2');
}
console.log('script start');
setTimeout(function () {
    console.log('setTimeout');
}, 0)
async1();
new Promise(function (resolve) {
    console.log('promise1');
    resolve();
}).then(function () {
    console.log('promise2');
});
console.log('script end'); 

微信图片_20210526095947.png

例题4
console.log('start');
let intervalId;
Promise.resolve().then(() => {
    console.log('p1');
}).then(() => {
    console.log('p2');
});
setTimeout(() => {
    Promise.resolve().then(() => {
        console.log('p3');
    }).then(() => {
        console.log('p4');
    });
    intervalId = setInterval(() => {
        console.log('interval');
    }, 3000);
    console.log('timeout1');
}, 0);
答案:"start" "p1" "p2" "timeout1" "p3" "p4" "interval"

例题5
setTimeout(() => {
    console.log('a');
});
Promise.resolve().then(() => {
    console.log('b');
}).then(() => {
    return Promise.resolve('c').then(data => {
        setTimeout(() => {
            console.log('d')
        });
        console.log('f');
        return data;
    });
}).then(data => {
    console.log(data);
});
答案:"b" "f" "c" "a" "d"
例题6
function func1() {
    console.log('func1 start');
    return new Promise(resolve => {
        resolve('OK');
    });
}
function func2() {
    console.log('func2 start');
    return new Promise(resolve => {
        setTimeout(() => {
            resolve('OK');
        }, 10);
    });
}
console.log(1);
setTimeout(async () => {
    console.log(2);
    await func1();
    console.log(3);
}, 20);
for (let i = 0; i < 90000000; i++) {} //循环大约要进行80MS左右
console.log(4);
func1().then(result => {
    console.log(5);
});
func2().then(result => {
    console.log(6);
});
setTimeout(() => {
    console.log(7);
}, 0);
console.log(8);