阮一峰博客 :https://es6.ruanyifeng.com/#docs/promise
目录
一、学习promise的前置条件
1.1 区别实例对象和函数对象
1.2 两种类型的回调函数
1.2.1 同步回调
1.2.2 异步回调
1.3 JS中的error处理(错误处理)
1.3.1 常见的内置错误
1.3.2 错误的处理
1.3.3 错误对象的属性
二、promise的理解和使用
2.1 promise是什么?
2.2 promise解决的问题?
2.3 promise的特点?
2.4 promise的状态改变?
2.5 promise的缺点?
2.6 promise的基本流程?
2.7 Promise的API
2.7.1 Promise.resolve()
2.7.2 Promise.reject()
2.7.3 Promise.all()
2.7.4 Promise.race()
2.7.5 Promise.any()
2.7.6 Promise.prototype.then()
2.7.7 Promise.prototype.catch()
2.7.8 Promise.prototype.finally()
2.8 promise的使用
2.8.1 字节面试题,看代码写输出
2.8.2 字节面试题,看代码写输出 (与宏任务和微任务有关)
2.8.3 字节面试题,看代码写输出
2.8.4 字节面试题,看代码写输出 (async 和 await)
2.8.5 字节面试题,看代码写输出
2.8.6 字节面试题,看代码写输出 (与宏任务和微任务有关)
2.8.7 js如何实现异步操作,浏览器事件循环机制(宏任务和微任务)
2.8.8 把 ajax 封装成 promise 的形式
2.8.9 将一个典型回调风格的功能函数变为promise风格
2.8.10 用Promise实现传统的回调函数,尝试捕获错误;如何让两个异步函数并发执行。
2.8.11 手写promise.all
- 回调函数
- 定时器
- JS单线程
- Ajax
- 异常处理
- IO流
注意:
回调函数详解参考文章:https://blog.csdn.net/itcodexy/article/details/111027346
理解:立即执行,完全执行完了才结束,不会放入回调队列中。主程序A会因回调函数C的执行而阻塞
例1:A.B(参数,回调函数C),主程序A调用函数B,并传入回调函数C,主程序A会等待回调函数C执行完成
例2:forEach中的回调函数执行完(不会放入队列中,一上来就执行),才会执行后面的操作
打印结果:
理解:不会立即执行,会放入回调队列中将来执行。回调函数C的执行不会阻塞调用方A。
例1:A.B(参数,回调函数C)。把回调函数C放到另一个线程(进程)、甚至另一台机器上,因此回调函数的执行和我们主程序A的运行同时进行。
例2:setTimeout()内的是回调函数,会放入队列将来执行,故先执行了其后面的内容。
打印结果:
1) ReferenceError:引用的变量不存在
2)TypeError:数据类型不正确的错误
3)RangeError:数值不在其所允许的范围内
4)SyntaxError:语法错误
1)捕获错误 try ... catch(捕获try中的异常)
2)抛出错误:throw error (将异常抛出)
调用该函数时,捕获抛出的异常,并打印
Promise 是异步编程的一种解决方案,比传统的解决方案(回调函数和事件)更合理和更强大。
1)所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件的结果。(这个事件通常是一个异步操作)
2)从语法上,Promise是一个构造函数,用来生成Promise实例。
3)从功能上,Promise对象用来封装一个异步操作,可以获取其结果。
4)Promise提供统一的API,各种异步操作都可以用同样的方法进行处理。
例1:生成Promise实例
1)Promise
构造函数接受一个函数作为参数,该函数的两个参数分别是resolve
和reject
。它们是两个函数,由 JavaScript 引擎提供,不用自己部署;
2)resolve
函数的作用是,将Promise
对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;
3)reject
函数的作用是,将Promise
对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去
例2:
Promise
实例生成后,用then
方法接收resolved
状态和rejected
状态的回调函数。
1)then
方法可以接受两个回调函数作为参数。这两个函数的参数值对应promise实例中传入的参数。这两个函数都是可选的,不一定要提供。
2)第一个回调函数是:Promise
对象的状态变为resolved
时调用(即成功时调用)
3)第二个回调函数是:Promise
对象的状态变为rejected
时调用(即失败时调用)
补充:resolved
统一只指fulfilled
状态,不包含rejected
状态。
1) 解决回调地狱问题:在promise之前,我们使用回调函数来发送异步请求,回调函数的问题是当我们有很多请求,并且下一个请求要依赖上一个请求的结果,这样会导致我们的请求
层层嵌套,使得代码非常臃肿,可读性差,容易产生bug导致所谓的回调地狱。
解决:使用Promise的then方法
2)任何时候都可以得到fulfilled
(已成功)或 rejected
(已失败)的结果,如果状态改变已经发生了,你再对Promise
对象添加回调函数,也会立即得到这个结果。这与事件
(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。
1)对象的状态不受外界影响。Promise
对象代表一个异步操作,有三种状态:pending
(进行中)、fulfilled
(已成功)和rejected
(已失败)。只有异步操作的结果,可以决定当前
是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise
这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。
2)一旦状态改变,就不会再变,并且任何时候都可以得到操作成功或失败的结果。Promise
对象的状态改变,只有两种可能:从pending
变为fulfilled
和从pending
变为rejected
。只要
这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对Promise
对象添加回调函数,也会立即得到这个结
果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。
3) Promise 新建后就会立即执行。下面代码中,Promise 新建后立即执行,所以首先输出的是“开始执行promise”。然后将在当前脚本所有同步任务执行完后,then
方法指定的回调函数才会执行,所以“操作成功”最后输出。
pending
(进行中)-> fulfilled
(已成功)pending
(进行中)-> rejected
(已失败)注意:
Promise
,一旦新建它就会立即执行,无法中途取消。Promise
内部抛出的错误,不会反应到外部。pending(进行中)
状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。注意:
失败时,执行reject(),
返回一个 Promise 实例,该实例的状态为rejected。
then
方法后面再调用另一个then
方法。Promise自身的方法:
reject
的理由,变成后续方法的参数(例如 reject(e).catch(console.log(e)))。p1
、p2
之中有一个先改变状态,p
的状态就跟着改变。先改变状态的返回值,传递给p
的回调函数。fulfilled
还是rejected
,实例p才会结束。成功和失败的信息以数组的形式传给pPomise原型上的方法:
then
方法的第一个参数是resolved
状态的回调函数,第二个参数是rejected
状态的回调函数,它们都是可选的。then
方法返回的是一个新的Promise
实例(注意,不是原来那个Promise
实例)。因此可以采用链式写法,即then
方法后面再调用另一个then
方法。try/catch
代码块不同的是,如果没有使用catch()
方法指定错误处理的回调函数,Promise 对象抛出的错误不会传递到外层代码,即不会有任何反应。通俗的说法就是“Promise 会吃掉错误”。finally
方法的回调函数不接受任何参数,表明finally
方法里面的操作与状态无关,不依赖于 Promise 的执行结果。finally
本质上是then
方法的特例, 有了finally
方法,不需要为成功和失败两种情况各写一次, 只需要写一次。根据参数不同,分为以下四种情况
1)参数是一个Promise的实例:Promise.resolve将不作任何修改,原封不动的返回这个实例。
2)参数是一个thenable对象:Promise.resolve方法会将这个对象转为Promise对象,然后立即执行thenable对象的then方法。
thenable对象指的是具有then方法的对象
3)参数是一个原始值,或者是一个不具有then方法的对象:返回一个新的Promise对象,状态为Resolved。
4)不带任何参数:直接返回一个状态为resolved的Promise对象,所以如果希望得到一个Promise对象,最直接的方法就是直接调用Promise.resolve方法。
第四种情况下,需注意:立即resolve的Promise对象是在本轮“事件循环”event loop结束时,不是在下一轮“事件循环”开始时,如下面代码所示:
返回一个新的 Promise 实例, 其参数会原封不动地作为reject
的理由,变成后续方法的参数(例如 reject(e).catch(console.log(e)))
将多个 Promise 实例p1、p2,包装成一个新的 Promise 实例p。全部成功,p才会成功;有一个失败p就失败,返回失败结果。
以数组的形式返回所有promise的执行结果。
接受一个数组作为参数,p1
、p2
、p3
都是 Promise 实例。
p
的状态由p1
、p2
、p3
决定,分成两种情况:
1)只有p1
、p2
、p3
的状态都变成fulfilled(已成功)
,p
的状态才会变成fulfilled
,此时p1
、p2
、p3
的返回值组成一个数组,传递给p
的回调函数。
2)只要p1
、p2
、p3
之中有一个被rejected(已失败)
,p
的状态就变成rejected
,此时第一个被reject
的实例的返回值,会传递给p
的回调函数。
注意:如果p1定义了catch
方法,被rejected了,
并不会触发Promise.all()
的catch
方法。
(因为p1执行完catch
方法后,也会变成resolved
,导致Promise.all()
方法参数里面的两个实例都会resolved,
而不会触发Promise.all()
的catch
方法。)
例子:Promise.all([p1,p2]) 。 p1自己捕获了错误,所以对p而言,p1和p2状态都是已成功
如果p1没有捕获错误,则p1失败,直接返回错误值,p视为失败。只打印错误信息
将多个 Promise 实例p1、p2,包装成一个新的 Promise 实例p。只要p1
、p2
之中有一个先改变状态,p
的状态就跟着改变。先改变状态的返回值,传递给p
的回调函数。
例子:Promise.race([p1,p2]) 。p1先执行完,p随p1的状态改变
将多个 Promise 实例p1、p2,包装成一个新的 Promise 实例p。
只要有一个成功,p就会成功,先成功的返回值传给p;所有都失败,p才会变成失败状态,返回错误AggregateError。
例子:Promise.any([p1,p2]) 。p1和p2都失败,返回一行错误提示。
用来对成功或失败的状态进行回调。 then
方法的第一个参数是resolved
状态的回调函数,第二个参数是rejected
状态的回调函数,它们都是可选的。
then
方法返回的是一个新的Promise
实例(注意,不是原来那个Promise
实例)。因此可以采用链式写法,即then
方法后面再调用另一个then
方法。
如果还需要调用then方法,需要return一个结果,才能被下一个then方法拿到
例如:如果第一个then 不返回value,第二个then获取不到value。
注意:对同一个promise对象的多次then方法并不是promise链
用于指定发生错误时的回调函数。处理错误,可以放在链的最后,用来处理整条链的错误。
问题:跟传统的try/catch
代码块不同的是,如果没有使用catch()
方法指定错误处理的回调函数,Promise 对象抛出的错误不会传递到外层代码,即不会有任何反应。通俗的说法就是“Promise 会吃掉错误”。
例如:p1对象出现错误,但是2s后,仍然执行了下面的代码,输出了123
不管 Promise 对象最后状态如何,都会执行finally。finally
方法的回调函数不接受任何参数,表明finally
方法里面的操作与状态无关,不依赖于 Promise 的执行结果。finally
本质上是then
方法的特例, 有了finally
方法,不需要为成功和失败两种情况各写一次, 只需要写一次。
例1:即使p1被rejected了,finally方法的回调函数仍然执行了
根据代码说出执行结果(大概20+道题)
https://blog.csdn.net/qq_42033567/article/details/108129645?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522162071744216780265428419%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=162071744216780265428419&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_v2~rank_v29-2-108129645.pc_search_result_hbase_insert&utm_term=%E5%B8%B8%E8%A7%81%E7%9A%84promise%E7%9A%84%E9%9D%A2%E8%AF%95%E9%A2%98
console.log('begin')
setTimeout(() => { console.log('setTimeout 1')
Promise.resolve().then(() => {
console.log('promise 1')
setTimeout(() => { console.log('setTimeout2 between promise1&2') })
}).then(() => {
console.log('promise 2')
}) }, 0);
console.log('end')
输出:begin -> end -> setTimeout 1 -> promise 1 -> promise 2 -> setTimeout2 between promise1&2
宏任务类型:包括整体代码script,setTimeout,setInterval(循环),setImmediate
微任务类型:Promise,process.nextTick
console.log(1);
setTimeout(() => {
console.log(2);
Promise.resolve().then(() => {
console.log(3)
});
});
new Promise((resolve, reject) => {
console.log(4)
resolve()
}).then(() => {
console.log(5);
})
setTimeout(() => {
console.log(6);
})
console.log(7);
输出:1 -> 4 -> 7 -> 5 -> 2 -> 3 -> 6
const first = () =>
new Promise((resovle, reject) => {
console.log(1)
const p = new Promise((resovle, reject) => {
console.log(2)
setTimeout(() => {
console.log(3)
resovle(4)
}, 0)
resovle(5)
})
resovle(6)
p.then(arg => {
console.log(arg)
})
})
first().then(arg => {
console.log(arg)
})
console.log(7)
输出:1 -> 2 -> 7 -> 5 -> 6 -> 3
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2() {
console.log('async2 start');
return new Promise((resolve, reject) => {
resolve();
console.log('async2 promise');
})
}
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0);
async1();
new Promise(function(resolve) {
console.log('promise1');
resolve();
}).then(function() {
console.log('promise2');
}).then(function() {
console.log('promise3');
});
console.log('script end')
输出:srcipt start -> async1 start -> async2 start -> async2 promise -> promise1 -> srcipt end ->
promise2 -> promise3 -> async1 end -> setTimeout
1)例1
async function async1() {
console.log("async1 start");
await async2();
console.log("async1 end");
}
async function async2() {
console.log("async2");
}
async1();
console.log('start')
输出结果:
async1 start
async2
start
async1 end
分析过程:
这里可以理解为await后面的语句相当于放到了new Promise中,下一行及之后的语句相当于放在Promise.then中。
2)例2
async function async1() {
console.log("async1 start");
await async2();
console.log("async1 end");
setTimeout(() => {
console.log('timer1')
}, 0)
}
async function async2() {
setTimeout(() => {
console.log('timer2')
}, 0)
console.log("async2");
}
async1();
setTimeout(() => {
console.log('timer3')
}, 0)
console.log("start")
输出结果:
async1 start
async2
start
async1 end
timer2
timer3
timer1
分析过程:
3)例3
async function async1 () {
console.log('async1 start');
await new Promise(resolve => {
console.log('promise1')
})
console.log('async1 success');
return 'async1 end'
}
console.log('srcipt start')
async1().then(res => console.log(res))
console.log('srcipt end')
输出结果:
script start
async1 start
promise1
script end
分析过程:
这里需要注意的是在async1
中await
后面的Promise是没有返回值的,也就是它的状态始终是pending
状态,所以在await
之后的内容是不会执行的,也包括async1
后面的 .then
。
new Promise(() => {
throw new Error()
}).catch(() => {
console.log(1);
}).then(() => {
console.log(2);
})
输出结果:1 -> 2
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0);
new Promise(function(resolve) {
console.log('promise1');
resolve();
}).then(function() {
console.log('promise2');
}).then(function() {
console.log('promise3');
});
console.log('script end');
// script start -> promise1 -> script end -> promise2 -> promise3 -> setTimeout
输出结果: script start -> promise 1 -> script end -> promise 2 -> promise 3 -> setTimeout
(1)回调函数
概念: 封装了异步操作的函数接受一个匿名函数作为参数,当异步操作执行完后调用这个传递进来的匿名函数
缺点:容易出现多层的嵌套(回调地狱问题),例如ajax请求来的数据作为下一个ajax请求的参数,一个异步完成的结果作为另一个异步操作函数的参数
(2)promise
(3)async await
为了简便起见没有考虑浏览器的兼容问题
1)使用get请求(在url尾部传递参数)
function getJson(url){
return new Promise((resolve, reject) => {
//1. 创建XMLHttpRequest对象
var xhr = new XMLHttpRequest();
//2. 创建请求
xhr.open("GET",url,true);
//3. 监听readyState状态,获取数据
xhr.onreadystatechange = function(){
if(xhr.readyState === 4){//3.1已获取响应数据
if(xhr.status === 200){ //3.2服务器成功处理请求
resolve(JSON.parse(xhr.responseText)); //返回响应数据
}else{//3.3请求失败(需要返回失败时的状态码)
reject({
status : xhr.status,
data : JSON.parse(xhr.responseText)
});
}
}
}
//4. 发送请求
xhr.send();
})
}
2)使用post请求(send方法中传递参数,必须设置请求头content-type)
function getJson(url,data){
return new Promise((resolve, reject) => {
//1. 创建XMLHttpRequest对象
var xhr = new XMLHttpRequest();
//2. 创建请求
xhr.open("POST",url,true);
//3. 设置请求头content-type
xhr.setRequestHeader("Content-type","application/x-www-form-urlencoded");
//4. 监听readyState状态,获取数据
xhr.onreadystatechange = function(){
if(xhr.readyState === 4){//3.1已获取响应数据
if(xhr.status === 200){ //3.2服务器成功处理请求
resolve(JSON.parse(xhr.responseText)); //返回响应数据
}else{//3.3请求失败(需要返回失败时的状态码)
reject({
status : xhr.status,
data : JSON.parse(xhr.responseText)
});
}
}
}
//4. 发送请求
xhr.send(JSON.stringify(data));
})
}
这里实际上问的很基础了,改写回调函数直接new 一个Promise对象,判断状态执行resolve跟reject即可,后面捕获错误我直接在Promise里面捕获了,后续了解到在Promise外部 await的时候捕获更好一点。异步函数并发执行直接采用Promise.all([]),即可实现。
传统异步函数,必须传入回调函数。
(1)方式一:有了Promise之后,就这么封装一下
连续.then().then()没有回调黑洞了,变成了火车黑洞。
(2)方式二:sync/await
它能够等异步执行完毕,再执行后面的语句。
它能够让我们以写同步的方法写异步: