从零开始了解Promise(超详细)
这里引用了一下金色小芝麻博主的图片!!!!
一、Promise简单了解
Promise是JavaScript中的一种异步编程模式,用于处理异步操作和编写更优雅的异步代码。
Promise最早是由CommonJS社区提出的,并在ES6(ECMAScript 2015)规范中正式引入JavaScript语言。它的目的是解决回调地狱(callback hell)问题,即当存在多个嵌套的异步操作时,通过回调函数传递的方式容易导致代码可读性差、难以维护的问题。
Promise的核心概念是代表一个异步操作的对象,该对象可以处于三种状态之一:未完成(pending)、已成功(fulfilled)和已失败(rejected)。当异步操作完成时,Promise对象会从未完成状态转为已成功或已失败状态,并且会执行相应的处理程序。
使用Promise可以通过链式调用的方式编写更加清晰、易于理解的异步代码。它提供了以下几个主要方法:
then(onFulfilled, onRejected)
:用于注册异步操作成功或失败时的处理程序。catch(onRejected)
:用于捕获异步操作失败时的异常。finally(onFinally)
:无论异步操作的状态如何,都会执行的处理程序。
Promise通过这些方法的组合使用,可以实现更加复杂的异步操作逻辑,例如并行执行多个异步操作、按顺序执行多个异步操作等。
Promise:ES6新增的内置类「内置构造函数」,它基于“承诺者”设计模式,有效管理JS中的异步编程代码,避免回调地狱<br />
阶段一:Promise全系列基础知识 && async/await的基础知识<br />
阶段二:Promise异步微任务处理机制 && async/await异步微任务处理机制<br />
------------<br />
页面中的数据大部分都是“动态绑定”的「HTML->DHTML」,而数据一般都是基于ajax/fetch等相关技术从服务器获取的!在这里会涉及一个概念:ajax的并行和串行!!<br />
ajax并行:可以“同时发送”多个ajax请求「请求之间没有依赖」,哪个请求先完成,则优先处理哪一个!真实项目中,偶尔需要在并行的基础上,扩展一个进阶需求 “等待所有请求都完毕,整体处理啥事情”!<br />
ajax串行:多个请求之间“存在依赖”,只有等上一个请求成功,下一个请求才可以发送!<br />
异步操作的管理方案:<br />
- 回调函数:在异步处理结束后,触发回调函数执行
$.ajax(…)
setTimeout(function(){
….
},1000);
这种方案经常会导致回调地狱!!
- 基于Promise管理异步操作「以后真实项目中,大部分异步操作,都需要基于Promise管理(或者基于async/await调用执行)」
JQ中基于‘回调函数’的方式,对ajax四部进行了封装 $.ajax(options) options配置项意思
$.ajax{
url:'/api/list',
method:'GET',
dataType:'json',
success(value){
//请求成功时候,触发success回调函数,「失败就出发error回调函数」
//value就是从服务器获取结果;
}
}
基于Promise来管理异步编程(封装ajax操作):axios/fetch
const ajax = function ajax(url) {
return new Promise(resolve => {
let xhr = new XMLHttpRequest;
xhr.open('GET', url, true);
xhr.onreadystatechange = () => {
if (xhr.readyState === 4 && xhr.status === 200) {
resolve(JSON.parse(xhr.responseText));
}
};
xhr.send();
});
};
需求一:
打开一个页面,我们有三部分信息是需要动态绑定「发送三个请求」,三部分信息没有依赖「同时发送」ajax并行:多个请求之间没有依赖,同时发送多个请求,谁先获取到就先处理谁;并行中出现一个特殊的需求:三个请求都成功,做一些自己的事情
let num = 0;
const complete = function complete(){
}
$.ajax{
url:'/api/a',
success(value){
console.log('第一个请求成功',value);
}
}
$.ajax{
url:'/api/a',
success(value){
console.log('第二个请求成功',value);
}
}
$.ajax{
url:'/api/a',
success(value){
console.log('第三个请求成功',value);
}
}
基于Promise优化
let p1 = ajax('/api/a').then(value => {
console.log('第一个请求成功:', value);
});
let p2 = ajax('/api/b').then(value => {
console.log('第二个请求成功:', value);
});
let p3 = ajax('/api/c').then(value => {
console.log('第三个请求成功:', value);
});
Promise.all([p1, p2, p3]).then(values => {
console.log('请求都成功了:', values); //values存储每一个成功的结果
});
需求二:
我们想获取学生在班级的排名1.首先获取学生的信息 /api/a 一>包含一个id「记录学生的学籍号」2.用获取的信息,获取他的成绩 /api/b?id=学籍号 -> [110,118,98]3.用获取的成绩,在去获取班级排名 /api/c?score=110,118,98 -> 2分析:三个请求之间是有依赖的,只有上一个请求成功,才能基于获取的结果,发送下一个请求;这就是ajax串行如果是基于“回调函数”的方法管理ajax异步请求,在ajax串行操作中,一定会出现“回调中嵌套回调”「回调地狱」
基于Promise解决回调地狱
基于Async/Await把异步操作搞成和同步差不多
(async function () {
let value = await ajax('/api/a');
let { id } = value;
value = await ajax('/api/b?id=' + id);
value = await ajax('/api/c?score=' + value);;
console.log('该学员的排名是:', value);
})();
模拟经典的“回调地狱”
setTimeout(() => {
console.log('A');
setTimeout(() => {
console.log('B');
setTimeout(() => {
console.log('C');
}, 3000);
}, 2000);
}, 1000);
基于Promise优化
二、 Promise基础知识详细理解
1.使用方法:
Let p1 = new Promise(executor)
Promise() //Uncaught TypeError: Promise constructor cannot be invoked without'new’ 必须NEW执行,否则报错!!不能作为普通函数执行new Promise() //Uncaught TypeError: Promise reso lver undefined is not a function NEw的时候,必须在Promise中传递executor函数
2.p1是Promise构造函数的实例
1)+ 私有属性/方法
[[Promisestatel]:实例的状态 pending准备「默认」、fulfilled成功、rejected失败[[PromiseResult]]:实例的值 undefined「默认」、成功的结果、失败的原因
2)+公有属性/方法 Promise.prototype
+then+catch+finally+Symbol(Symbol. tostringTag):"Promise"
p1._proto_ -> Promise. prototype -> Object.prototype
展开说明一下:
- then(onFulfilled, onRejected):
const promise = new Promise((resolve, reject) => {
// 异步操作的代码
resolve('Success');
});
promise.then(result => {
console.log(result); // 输出: Success
}).catch(error => {
console.error(error); // 不会执行
});
在上述示例中,通过创建一个Promise对象,并在执行器中调用resolve方法,将Promise状态变为已成功。然后,通过调用then方法注册了一个处理程序,在Promise状态变为已成功时被调用,并输出结果值。
- catch(onRejected):
const promise = new Promise((resolve, reject) => {
// 异步操作的代码
reject(new Error('Something went wrong.'));
});
promise.then(result => {
console.log(result); // 不会执行
}).catch(error => {
console.error(error.message); // 输出: Something went wrong.
});
在上述示例中,通过创建一个Promise对象,并在执行器中调用reject方法,将Promise状态变为已失败,并传递一个错误作为原因。然后,通过调用catch方法注册了一个处理程序,在Promise状态变为已失败时被调用,并捕获并输出错误信息。
- finally(onFinally):
const promise = new Promise((resolve, reject) => {
// 异步操作的代码
resolve('Success');
});
promise.finally(() => {
console.log('Cleanup'); // 总是执行
}).then(result => {
console.log(result); // 输出: Success
});
在上述示例中,通过创建一个Promise对象,并在执行器中调用resolve方法,将Promise状态变为已成功。然后,通过调用finally方法注册了一个处理程序,它无论Promise状态如何都会执行。最后,通过调用then方法注册了一个处理程序,在Promise状态变为已成功时被调用,并输出结果值。
3)把类做为普通对象,设置的静态私有属性 Promise. xxx
展开说明一下:
- Promise.resolve(value):
const resolvedPromise = Promise.resolve(42);
resolvedPromise.then(result => {
console.log(result); // 输出: 42
});
在上述示例中,通过Promise.resolve方法创建了一个已经处于"已成功"状态的Promise对象,并将其解析为值42。后续的then方法会接收到该值,并将其打印出来。
- Promise.reject(reason):
const rejectedPromise = Promise.reject(new Error('Something went wrong.'));
rejectedPromise.catch(error => {
console.error(error.message); // 输出: Something went wrong.
});
在上述示例中,通过Promise.reject方法创建了一个已经处于"已失败"状态的Promise对象,并将其拒绝为一个错误原因。后续的catch方法会捕获到该错误,并将错误信息打印出来。
- Promise.all(iterable):
const promise1 = Promise.resolve(10);
const promise2 = Promise.resolve(20);
const promise3 = Promise.resolve(30);
const allPromises = Promise.all([promise1, promise2, promise3]);
allPromises.then(results => {
console.log(results); // 输出: [10, 20, 30]
});
在上述示例中,通过Promise.all方法将多个Promise对象组合在一起,并返回一个新的Promise对象。该新的Promise对象在所有输入的Promise对象都变为已成功状态时,自身也会变为已成功状态,并返回一个包含所有结果值的数组。
- Promise.race(iterable):
const promise1 = new Promise(resolve => setTimeout(resolve, 200, 'A'));
const promise2 = new Promise(resolve => setTimeout(resolve, 100, 'B'));
const racePromise = Promise.race([promise1, promise2]);
racePromise.then(result => {
console.log(result); // 输出: B
});
在上述示例中,通过Promise.race方法将多个Promise对象组合在一起,并返回一个新的Promise对象。该新的Promise对象会响应最先完成的Promise对象,并使用该对象的结果值来解析。
- Promise.allSettled(iterable):
const promise1 = Promise.resolve(42);
const promise2 = Promise.reject(new Error('Something went wrong.'));
const promise3 = new Promise(resolve => setTimeout(resolve, 200, 'Done'));
const allSettledPromise = Promise.allSettled([promise1, promise2, promise3]);
allSettledPromise.then(results => {
console.log(results);
/*
输出:
[
{ status: 'fulfilled', value: 42 },
{ status: 'rejected', reason: Error: Something went wrong. },
{ status: 'fulfilled', value: 'Done' }
]
*/
});
在上述示例中,通过Promise.allSettled方法将多个Promise对象组合在一起,并返回一个新的Promise对象。该新的Promise对象会等待所有输入的Promise对象都已完成,不管是已成功还是已失败,然后返回一个包含每个Promise对象状态和结果值/原因的数组。
3.创建实例的5种方法
** 要研究的话题:如何创建实例,以及如何修改其状态和值!**
@1 基于构造函数执行来创建实例
let p1=new Promise(executor);+必须是一个函数+new Promise的时候,会”立即执行"executor函数「同步」+在此函数中,我们一般处理异步编程的代码
+接收Promise内部传递给他的两个实参:resolve / reject
+resolve / reject都是一个小函数作用是修改实例的状态和值<br />
resolve(value):把实例的状态改为fulfilled,值改为value
reject(reason):把实例的状态改为rejected,值改为 reason
+一但状态被改为成功或者失败了,则不能再次修改!+如果executor在执行过程中报错,而且此时实例状态还是pending,则把实例的状态改为rejected,值是报错原因「内部捕获了异常信息(try/catch),所以即便报错,对executor外部的代码执行是没有影响的」要研究的话题:知道了实例的状态和值,有啥用?1) 知道了实例的状态和值,我们就可以调用”then方法,确定成功和失败各干什么;p. then (onfulfilled, onrejected)+ onfulfilled:在实例状态为成功的时候触发执行+onrejected:在实例状态为失败的时候触发执行+都会把实例的值传递给这两个函数+同一个实例,可以多次调用then方法,最后会把每一次调用传递的onfulfilled/onrejected都执行【不推荐】
**测试 **
Let p1 = new Promise((resolve, reject) =>{
console. Log(resolve, reject);
});
console. Log (p1);
Let p1 = new Promise((resolve, reject) =>{
//空
});
console. Log (p1);
Let p1 = new Promise((resolve, reject) =>{
resolve(100);
});
console. Log (p1);
Let p1 = new Promise((resolve, reject) =>{
reject(100);
});
console. Log (p1);
Let p = new Promise((resolve) =>{});
@2 基于Promise.resolve/reject方法创建实例
+Promise. resolve(10) ->创建一个状态为fulfilled,值是10的实例+Promise. reject(0)->创建一个状态为rejected,值是0的实例
@3 每一次执行then方法,都会返回一个全新的 Promise实例
目的:实现“THEN链”机制(也就是 .then之后还可以再 .then...)
let p2 = p1. then (onfulfilled, onrejected)
- +p2是一个新的promise实例
- +p2的状态和值,取决于onfulfilled或者onrejected中的,任意一个方法执行,我们观察执行的细节和结果,来决定p2的状态和值!!
- +首先看方法执行是否报错;如果执行报错了,则p2就是rejected,值是报错原因!
- +再看方法执行的返回值,是否是单独的promise实例「别名:PP」
- +不是:则p2是fulfilled,值就是函数的返回值
- +是:则p2的状态和值,和PP保持一致!!
1)THEN链 的穿透机制 catch/finallyp1. then (onfulfilled, onrejected)+如果onfulfilled, onrejected 没有传递,这顺延到下一个THEN “同等状态”要执行的方法+ 原理:如果我们不设置对应的函数,Promise内部会默认设置一个函数,实现 状态/值 的顺延
:::tips
默认加了->
:::
p.catch(onfulfilled) +等价与 p. then (null, onrejected)p.finally(onfulfilled)**+**不论成功还是失败,都会把onfinally执行「一般不用」真实项目开发中,我们使用Promise,then中一般只放onfulfilled「只放成功做的事情」,在THEN链的末尾,设置一个catch,处理失败的情况;根据THEN的穿透机制,中间不论那个环节出现失败的实例,都会顺延至最后一个catch进行失败的统一处理!!
@4.Promise.all/any/ race( [promises])
+都会返回一个新实例「别名p,或者称之为总的实例」+p的状态和值,取决于 [promises]实例集合中的,每个实例的状态和值.+ [promises]集合中如果有一项不是promise实例,则要变为:状态为成功,值是本身的实例
+all:所有实例为成功,总实例p才是成功「值:按集合顺序依次存储每一项的值」,如果有一个实例为失败,则总实例p就是失败「值是失败项的值」
:::tips
:::
+any:兼容性比较差,它是ES靠后版本(11/12)中新增的;只要有一个成功,总实例p就是成功的「值是成功这一项的值」,如果所有项都失败了,则总实例p是失败的,「值是:all promi were rejected」
+race: 谁先处理完就听谁的,「把最快处理完毕的结果同步给总实例p」
@5.创建promise 实例的第五种方案:
把函数基于async修饰,函数返回值,默认会变成一个promise实例
- +函数执行如果报错,则返回失败的实例,值是报错原因
- +不报错,再看返回值
- +返回的是一个新的promise实例「别名pp」,则pp的状态和值会同步给返回的实例
- +否则返回的一定是状态为成功,值是返回值的实例
async/await:ES8新增的,Promise「+generator」的语法糖,目的是让Promise用起来更简单
- +只能作用在函数身上
- +async
- 作用1:让函数返回一个promise实例
- 作用2「主要」:想用await,则所在的函数必须经过async修饰
- +await promise实例;
- +如果后面不是promise实例,则默认变为状态为成功,值是本身的实例
- +“当前上下文”中,遇到await,在await“下面”的代码暂时不能执行,需要等待await“后面”的实例状态为成功,下面代码才可以执行!
// 模拟2秒中从服务器获取数据的操作
const query = function query() {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject([100, 200, 300]);
}, 2000);
});
};
Uncaught (in promise)…….控制台看到这种错误,属于创建了一个失败的promise实例,但是我们并没有对失败的情况做处理,所以出现这个错误「但是这个错误不会杀死主线程(换句话说,不影响其它代码的执行)」await后面的实例如果是失败的,则当前上下文,其下面的代码不会执行「也不赋值给value」;但是以为没做失败的处理,控制台爆红了「不好看」;所以我们期望可以做一下处理..
(async function () {
await 10; //=> await Promise.resolve(10);
})();
(async function () {
console.log(1);
try {
let value = await query();
console.log(2);
console.log('成功:', value);
} catch (_) {
// await异常捕获「后面实例是失败的」:这里做的事情,相当于promise中的onrejected/catch!
console.log('失败:', _);
}
// 下面的代码会继续执行,类似于promise中finally
console.log('处理完毕');
})();
三、回调函数的详解
回调函数:把函数做为值,传递给另外一个函数,在另外一个函数执行的某个阶段,可以把我传递的这个函数执行「改变this\传值\接收返回值」 啥时候用回调函数:但凡在某个程序执行到某个阶段,我们想做一些自己的事情,我们都可以基于回调函数,把自己要做的事情传递进去!!// + 一般用于封装公共方法// + 异步处理
$.ajax({
url:'/api/xxx',
success(){
// 这里是我们要干的事情
}
});
const each = function each(obj, callback) {
let keys = Reflect.ownKeys(obj);
keys.forEach(key => {
callback(obj[key], key);
});
};
let obj = {
10: 100,
x: 200,
name: '哈哈哈',
[Symbol()]: '额呵呵'
};
each(obj, (value, key) => {
// 我要干的事情
console.log(value, key);
});
setTimeout(()=>{
// ...
},2000);
转载自:https://juejin.cn/post/7236196894987436069