Promise新版学习大纲
Promise
准备
函数对象与实例对象
1.函数对象: 将函数作为对象使用时, 简称为函数对象。
2.实例对象: new 构造函数或类产生的对象, 我们称之为实例对象。
//函数对象
function Person (name,age){
this.name = name
this.age = age
}
Person.a = 1 //将Person看成一个对象 这是给它添加一个a属性并且赋值为1
//实例对象 --p1是Person的实例对象
const p1 = new Person('老刘',18)
console.log(p1);
注意:每一个函数对象有一个不可修改的属性叫name
Person.name = 'tom' //这是错误的 因为name值是函数的名(Person)
回调函数的分类
什么是回调?
—①我们定义的函数,②我们没有调用,③最终函数执行了。
两种回调函数
同步的回调函数:
理解: 立即在主线程上执行, 不会放入回调队列中。
例子: 数组遍历相关的回调函数 / Promise的executor函数
异步的回调函数:
理解: 不会立即执行, 会放入回调队列中以后执行(等主线程忙完)
例子: 定时器回调 / ajax(成功、失败时)的回调
//演示同步的回调函数
let arr = [1,3,5,7,9]
arr.forEach((item)=>{
console.log(item);
})
console.log('主线程的代码');
//演示异步的回调函数
setTimeout(()=>{
console.log('@');
},2000)
console.log('主线程');
错误类型的说明
js中错误类型有详细的划分,这里进一步理解 JS 中的错误 (Error) 和错误处理
mdn文档: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Error
错误的类型
Error: 所有错误的父类型 (当发生错误时,将显示下方的子错误类型)
ReferenceError: 引用的变量不存在
TypeError: 数据类型不正确
RangeError: 数据值不在其所允许的范围内–死循环
SyntaxError: 语法错误
//演示:ReferenceError: 引用的变量不存在
console.log(a);
//演示:TypeError: 数据类型不正确
//(声明一个demo常量,并且赋值一个函数,demo函数是能够被调用的,值是undefined,然后undefined再调用函数,当然显示类型不正确)
const demo = ()=>{}
demo()()
//演示:RangeError: 数据值不在其所允许的范围内
//(声明一个变量demo并赋值一个函数给他,同时函数内部再调用自己(递归函数),然后执行demo函数,然后无限的执行就会超出栈范围)
const demo = ()=>{demo()}
demo()
//演示:SyntaxError: 语法错误
console.log(1;
js中的错误处理
捕获错误: try{}catch(){}
抛出错误: throw error
如何捕获一个错误?
- try中放可能出现错误的代码,一旦出现错误立即停止try中代码的执行,调用catch,并携带错误信息
try {
console.log(1);
console.log(a); //js引擎捕获到错误
console.log(2);
} catch (error) {
console.log('代码执行出错了,错误的原因是:',error);
}
js中的错误处理_抛出错误
//如何抛出一个错误
function demo(){
const date = Date.now()
if(date % 2 === 0){
console.log('偶数,可以正常工作');
}else{
throw new Error('奇数,不可以工作!')
}
}
try {
demo()
} catch (error) {
debugger;
console.log('@',error);
}
错误对象
message属性: 错误相关信息
stack属性: 记录信息
//演示:ReferenceError: 引用的变量不存在
console.log(a);
//演示:TypeError: 数据类型不正确
const demo = ()=>{}
demo()()
//演示:RangeError: 数据值不在其所允许的范围内
const demo = ()=>{demo()}
demo()
//演示:SyntaxError: 语法错误
console.log(1;
Promise的理解和使用
Promise是什么?
抽象表达:
(1).Promise是一门新的技术(ES6提出的)
(2).Promise是 JS 中异步编程的新方案 ** (旧方案是谁? –**纯回调:只靠回调函数解决(ajax))
具体表达:
(1).从语法上来说: Promise是一个内置构造函数
(2).从功能上来说: Promise的实例对象可以用来 封装一个异步操作,并可以获取其成功/失败的值
总结:
Promise不是回调函数,是一个内置的构造函数,是程序员自己new调用的。
new Promise的时候,要传入一个回调函数,它是同步的回调,会立即在主线程上执行,它被称为executor函数 –该函数将在构造这个新
Promise
对象过程中,被构造函数执行每一个Promise实例都有3种状态:初始化(pending)、成功(fulfilled)、失败(rejected)
每一个Promise实例在刚被new出来的那一刻,状态都是初始化(pending)
executor函数会接收到2个参数,它们都是函数,分别用形参:resolve、reject接收
1.调用resolve函数会:
(1).让Promise实例状态变为成功(fulfilled)
(2).可以指定成功的value –resolve(‘ok’)
2.调用reject函数会:
(1).让Promise实例状态变为失败(rejected)
(2).可以指定失败的reason –reject(‘失败的原因’)
<script type="text/javascript">
//创建一个Promise实例对象
const p = new Promise((resolve,reject)=>{
// resolve('ok')
reject('失败的原因')
// console.log(resolve);
console.log(reject);
})
console.log('@',p); //一般不把Promise实例做控制台输出 @ Promise {<rejected>: 'ok'}
</script>
Promise的基本使用
重要语法
new Promise(executor) 构造函数
Promise.prototype.then 方法
基本编码流程
1.创建Promise的实例对象(pending状态), 传入executor函数
2.在executor中启动异步任务(定时器、ajax请求)
3.根据异步任务的结果,做不同处理:
3.1 如果异步任务成功了:
我们调用resolve(value), 让Promise实例对象状态变为成功(fulfilled),同时指定成功的value
3.2 如果异步任务失败了:
我们调用reject(reason), 让Promise实例对象状态变为失败(rejected),同时指定失败的reason
4.通过then方法为Promise的实例指定成功、失败的回调函数,来获取成功的value、失败的reason
注意:then方法所指定的:成功的回调、失败的回调,都是异步的回调。
关于状态的注意点:
1.三个状态:
pending: 未确定的——初始状态
fulfilled: 成功的——调用resolve()后的状态
rejected: 失败的——-调用reject()后的状态
2.两种状态改变
pending ==> fulfilled
pending ==> rejected
3.状态只能改变一次!!
4.一个promise指定多个成功/失败回调函数, 都会调用吗?
<script>
// 1.声明变量p,将Promise实例对象赋值给它,并且里边传入一个执行器函数executor --里边有两个参数resolve、reject
const p = new Promise((resolve,reject)=>{
// 2.函数体
setTimeout(() => {
resolve('我是服务器返回的数据')
reject('我是一些错误信息')
}, 2000);
}).then(
// 3.成功和失败后的回调
(value)=>{console.log('成功了',value);}, //成功的回调-异步
(reason)=>{console.log('失败了',reason);} //失败的回调-异步
)
console.log('@');
</script>
Promise与ajax配合使用
<!-- ajax配合使用 -->
<script>
const p = new Promise((resolve,reject)=>{
// 真正开启一个异步任务
const xhr = new XMLHttpRequest()
xhr.onreadystatechange = ()=>{
if(xhr.readyState === 4) {
//readyState为4代表接收完毕,接收的可能是:服务器返回的成功数据、服务器返回的错误
if (xhr.status >= 200 && xhr.status < 300){
resolve(xhr.response)
}else {
reject('请求出错')
}
}
}
xhr.open('GET','https://api.apiopen.top/api/sentences')
// !!将服务器数据转为json格式
xhr.responseType = 'json'
xhr.send()
})
p.then(
(value)=>{console.log('成功了1',value);}, //成功的回调-异步
(reason)=>{console.log('失败了1',reason);} //失败的回调-异步
)
console.log('@');
</script>
Promise封装ajax请求
需求:
每次请求都要输入很多数据比较麻烦因此定义一个 sendAjax() 函数,对xhr的 GET 请求进行封装:
1.该函数接收两个参数:url(请求地址)、data(参数对象)
2.该函数返回一个Promise实例
(1).若ajax请求成功,则Promise实例成功,成功的value是返回的数据。
(2).若ajax请求失败,则Promise实例失败,失败的reason是错误提示。
正常使用promise发送一个ajax请求:
// 1.声明变量p,将Promise实例对象赋值给它,并且里边传入一个执行器函数executor --里边有两个参数resolve、reject
const p = new Promise((resolve, reject) => {
// 2.实例化 xhr
const xhr = new XMLHttpRequest()
// 3.绑定监听
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300) resolve(xhr.response)
else reject('请求出错')
}
}
xhr.open('GET', 'https://api.apiopen.top/api/getImages?page=0&size=10')
xhr.responseType = 'json'
xhr.send()
})
p.then(
(value) => { console.log('成功了1', value); }, //成功的回调-异步
(reason) => { console.log('失败了1', reason); } //失败的回调-异步
)
console.log('@');
使用函数封装后:
// 1.定义函数,并准备俩参数接收地址和参数(data是参数对象)
function sendAjax(url, data) {
// 2.准备一个promise实例
const p = new Promise((resolve,reject) => {
// 3.实例化 xhr
const xhr = new XMLHttpRequest()
// 4.绑定监听
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300) {
resolve(xhr.response);
} else {
reject('请求出了问题');
}
}
}
// 5.整理参数 --需将 page=0&size=10 这种形式转化为 {page:0,size:10}这种形式才能放进地址中作为参数
let str = ''
for (let key in data) {
str += `${key}=${data[key]}&`
}
str = str.slice(0, -1)
xhr.open('GET', url + '?' + str)
xhr.responseType = 'json'
xhr.send(
})
return p
const x = sendAjax('https://api.apiopen.top/api/getImages',{page:0,size:10})
x.then(
(data)=>{console.log('成功了',data);},
(reason)=>{console.log('失败了',reason);}
)
封装ajax请求(纯回调方式)
需求:
定义一个sendAjax函数,对xhr的get请求进行封装
该函数接收4个参数:url(请求地址)、data(参数对象)、success(成功的回调)、error(失败的回调)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>封装一个简单的ajax(纯回调)</title>
</head>
<body>
<script type="text/javascript">
function sendAjax(url,data,success,error){
//实例xhr
const xhr = new XMLHttpRequest()
//绑定监听
xhr.onreadystatechange = ()=>{
if(xhr.readyState === 4){
if(xhr.status >= 200 && xhr.status < 300) success(xhr.response);
else error('请求出了点问题');
}
}
//整理参数
let str = ''
for (let key in data){
str += `${key}=${data[key]}&`
}
str = str.slice(0,-1)
xhr.open('GET',url+'?'+str)
xhr.responseType = 'json'
xhr.send()
}
sendAjax(
'https://api.apiopen.top/api/getImages',
{page:0,size:10},
(response) =>{
console.log('成功了',response);
},
(err) =>{console.log('第1次失败了',err);}
)
</script>
</body>
</html>
Promise的API
Promise构造函数:
new Promise (executor) {}
executor函数: 是同步执行的,(resolve, reject) => {}
resolve函数: 调用resolve将Promise实例内部状态改为成功(fulfilled)。
reject函数: 调用reject将Promise实例内部状态改为失败(rejected)。
说明: **excutor函数会在Promise内部立即同步调用,**异步代码放在excutor函数中。
Promise.prototype.then方法:
Promise实例.then(onFulfilled,onRejected)
onFulfilled: 成功的回调函数 (value) => {}
onRejected: 失败的回调函数 (reason) => {}
特别注意(难点):then方法会返回一个新的Promise实例对象
验证then的返回值
//验证:then方法会返回一个新的Promise实例对象。(暂且不研究返回的这个新Promise实例状态怎么变化)
const p = new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve(100)
},1000)
})
const x = p.then(
value => {console.log('成功了',value);},
reason =>{console.log('失败了',reason);}
)
console.log('50');
console.log(x); // pomise {<pending>}
Promise.prototype.catch方法:
Promise实例.catch(onRejected)
onRejected: 失败的回调函数 (reason) => {}
说明: catch方法是then方法的语法糖, 相当于: then(undefined, onRejected)
//Promise.prototype.catch方法
const p = new Promise((resolve,reject)=>{
setTimeout(()=>{
reject(100)
},1000)
})
p.then(
value => {console.log('成功了1',reason);},
reason => {console.log('失败了1',reason);}
)
p.catch(
reason => {console.log('失败了2',reason);}
)
Promise.resolve方法:
Promise.resolve(value)
说明: 用于快速返回一个状态为fulfilled或rejected的Promise实例对象
备注:当状态为rejected是因为value的值可能是:
(1)非Promise值 – 100、’abc’、{}、[]
(2)Promise值 – Promise实例(此时值是传入的promise实例,值成功或者失败)
例子:Promise.resolve方法生成了一个状态为失败的Promise对象实例 p
//Promise.resolve
const p0 = Promise.reject(-100)
const p = Promise.resolve(p0)
p.then(
value => {console.log('成功了',value);},
reason => {console.log('失败了',reason);}
)
Promise.reject方法:
Promise.reject方法(reason)
说明: 用于快速返回一个状态必为rejected的Promise实例对象
例子:Promise.reject方法即使传入一个状态为成功的Promise对象实例值也是失败的 即失败的原因是那个成功的promise对象实例
//Promise.reject
const p0 = Promise.resolve(100)
const p = Promise.reject(p0)
p.then(
value => {console.log('成功了',value);},
reason => {console.log('失败了',reason);}
)
Promise.all方法:
Promise.all(promiseArr)
promiseArr: 包含n个Promise实例的数组
说明: 返回一个新的Promise实例, 只有所有的promise都成功才成功, 只要有一个失败了就直接失败。
//Promise.all
const p1 = Promise.resolve('a')
const p2 = new Promise((resolve,reject)=>{
setTimeout(()=>{
reject('b') //有一个失败了就直接失败。
},500)
})
const p3 = new Promise((resolve,reject)=>{
setTimeout(()=>{
reject('c')
},2000)
})
const x = Promise.all([p1,p2,p3])
x.then(
value => {console.log('成功了',value);},
reason => {console.log('失败了',reason);}
)
Promise.race方法:
Promise.race(promiseArr)
promiseArr: 包含n个Promise实例的数组
说明: 返回一个新的Promise实例, 成功还是很失败?以最先出结果的promise为准。
//Promise.race
const p1 = Promise.reject('a')
const p2 = new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve('b')
},500)
})
const p3 = new Promise((resolve,reject)=>{
setTimeout(()=>{
reject('c')
},2000)
})
const x = Promise.race([p3,p1,p2])
x.then(
value => {console.log('成功了',value);},
reason => {console.log('失败了',reason);}
)
Promise的几个关键问题:
如何改变Promise实例的状态?
执行resolve(value): 如果当前是pending就会变为fulfilled
执行reject(reason): 如果当前是pending就会变为rejected
执行器函数(executor)抛出异常: 如果当前是pending就会变为rejected
<script type="text/javascript" >
const p = new Promise((resolve,reject)=>{
console.log(a); //浏览器引擎抛异常
// throw 900 //编码抛异常
})
p.then(
value => {console.log('成功了',value);},
reason => {console.log('失败了',reason);}
)
</script>
改变Promise实例的状态和指定回调函数谁先谁后?
- 都有可能, 正常情况下是先指定回调再改变状态, 但也可以先改状态再指定回调
//先指定回调,后改变状态(最常见)
const p = new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve('a')
},4000)
})
p.then(
value => {console.log('成功了',value);},
reason => {console.log('失败了',reason);}
)
如何先改状态再指定回调?
- 延迟一会再调用then()
//先改状态,后指定回调
const p = new Promise((resolve,reject)=>{
resolve(100)
})
setTimeout(()=>{
p.then(
value => {console.log('成功了',value);},
reason => {console.log('失败了',reason);}
)
},2000)
Promise实例什么时候才能得到数据?
- 如果先指定的回调, 那当状态发生改变时, 回调函数就会调用, 得到数据
- 如果先改变的状态, 那当指定回调时, 回调函数就会调用, 得到数据
then如何链式调用?
Promise实例.then()返回的是一个【新的Promise实例】
它的值和状态由什么决定?
简单表达: 由then()所指定的回调函数执行的结果决定
详细表达:
- 如果then所指定的回调返回的是非Promise值a:
那么【新Promise实例】状态为:成功(fulfilled), 成功的value为a
- 如果then所指定的回调返回的是一个Promise实例p:
那么【新Promise实例】的状态、值,都与p一致
- 如果then所指定的回调抛出异常:
那么【新Promise实例】状态为rejected, reason为抛出的那个异常
const p = new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve('a')
},1000)
})
p.then(
value => {console.log('成功了1',value); return Promise.reject('a')},
reason => {console.log('失败了1',reason);}
).then(
value => {console.log('成功了2',value);return true},
reason => {console.log('失败了2',reason); return 100}
).then(
value => {console.log('成功了3',value);throw 900},
reason => {console.log('失败了3',reason); return false}
).then(
value => {console.log('成功了4',value);return -100},
reason => {console.log('失败了4',reason);}
)
纯回调会引起的问题?
如下:可以发现代码非常的混乱,不利于查阅,且当再多发几次的请求后更加难以查阅
// 调用之前封装好的 sendAjax函数
sendAjax(
'https://api.apiopen.top/api/getImages',
{page:0,size:10},
(response) =>{
//成功的回调1
console.log('第一次成功了',response)
sendAjax(
'https://api.apiopen.top/api/getImages',
{page:0,size:10},
(response) =>{
//成功的回调2
console.log('第二次成功了' ,response)
sendAjax(
'https://api.apiopen.top/api/getImages',
{page:0,size:10},
(response) =>{
//成功的回调3
console.log('第三次成功了',response)
},
(err) =>{
// 失败的回调3
console.log('第三次失败了',err);
}
)
},
(err) =>{
// 失败的回调2
console.log('第二次失败了',err);
}
)
},
(err) =>{
// 失败的回调1
console.log('第1次失败了',err);
}
)
使用then的链式调用解决回调地狱
注意:then方法会返回一个新的Promise实例对象 但是因为其成功的value回调里还返回了 sendAjax()方法,该方法的返回值也是Promise实例对象,所以then返回的新Promise实例的状态、值,都与sendAjax()方法返回的promise一致 (详情见上方 ‘then如何链式调用‘ )
// 调用之前封装好的 sendAjax函数 (详情见上方 'Promise封装ajax请求')
//发送第1次请求
sendAjax('https://api.apiopen.top/api/getImages',{page:0})
/*
注意:then方法会返回一个新的Promise实例对象 但是因为其成功
的value回调里还返回了 sendAjax()方法,该方法的返回值也是
Promise实例对象,所以then返回的新Promise实例的状态、值,
都与sendAjax()方法返回的promise一致 (详情见上方 'then如何链式调用' )
*/
.then(
value => {
console.log('第1次请求成功了',value);
//发送第2次请求
return sendAjax('https://api.apiopen.top/api/getImages',{page:0})
},
reason => {console.log('第1次请求失败了',reason);}
)
.then(
value => {
console.log('第2次请求成功了',value);
//发送第3次请求
return sendAjax('https://api.apiopen.top/api/getImages',{page:0})
},
reason => {console.log('第2次请求失败了',reason);}
)
.then(
value => {console.log('第3次请求成功了',value);},
reason => {console.log('第3次请求失败了',reason);}
)
中断Promise链
原因:
代码同上的时候,假如将发送第1次请求的地址写错后,第二次、第三次依然会因为种种原因继续执行。
思路:
当使用promise的then链式调用时, 在中间中断, 不再调用后面的回调函数。
办法:
在失败的回调函数中返回一个pendding状态的Promise实例。在每次reason后加上 ‘ return new Promise(()=>{}) ’ 这样then返回的promise实例对象状态是pending就不会执行后边的value和reason
代码如下:
.then(
value => {
console.log('第1次请求成功了',value);
//发送第2次请求
return sendAjax('https://api.apiopen.top/api/getImages',{page:0})
},
reason => {console.log('第1次请求失败了',reason);return new Promise(()=>{})}
)
.then(
value => {
console.log('第2次请求成功了',value);
//发送第3次请求
return sendAjax('https://api.apiopen.top/api/getImages',{page:0})
},
reason => {console.log('第2次请求失败了',reason);return new Promise(()=>{})}
)
.then(
value => {console.log('第3次请求成功了',value);},
reason => {console.log('第3次请求失败了',reason);}
)
Promise的错误穿透
当使用promise的then链式调用时, 可以在最后用catch指定一个失败的回调,
前面任何操作出了错误, 都会传到最后失败的回调中处理了
备注:如果不存在then的链式调用,就不需要考虑then的错误穿透。
原理:底层帮我们在then里补上了一个失败的回调 reason => {throw reason}
又因为then所指定的回调抛出异常,那么新Promise实例状态为rejected, reason为抛出的那个异常
//另一个例子演示错误的穿透
const p = new Promise((resolve,reject)=>{
setTimeout(()=>{
reject(-100)
},1000)
})
p.then(
value => {console.log('成功了1',value);return 'b'},
reason => {throw reason}//底层帮我们补上的这个失败的回调
)
.then(
value => {console.log('成功了2',value);return Promise.reject(-108)},
reason => {throw reason}//底层帮我们补上的这个失败的回调
)
.catch(
// reason => {throw reason}
reason => {console.log('失败了',reason)}
)
Promise的优势
优势:
- 指定回调函数的方式更加灵活:
旧的: 必须在启动异步任务前指定(如之前用jQuery封装的get请求,需要提前写好成功和失败的回调)
promise: 启动异步任务 => 返回promie对象 => 给promise对象绑定回调函数(甚至可以在异步任务结束后指定)
- 支持链式调用, 可以解决回调地狱问题
(1)什么是回调地狱:
回调函数嵌套调用, 外部回调函数异步执行的结果是嵌套的回调函数执行的条件
(2)回调地狱的弊病:
代码不便于阅读、不便于异常的处理
(3)一个不是很优秀的解决方案:
then的链式调用
(4)终极解决方案:
async/await(底层实际上依然使用then的链式调用)
async/await的使用
- 注意:
await必须写在async函数中, 但async函数中可以没有await
如果await的Promise实例对象失败了, 就会抛出异常, 需要通过try…catch来捕获处理
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('a')
}, 1000)
})
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('b')
}, 2000)
})
const p3 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('c')
}, 4000)
})
// async function demo() {
/*当Promise实例对象p状态为失败时,
一旦try中的代码发生了抛异常,
将会携带异常来到catch,此时err就是抛出的'a'
*/
//(记得前边加分号)
; (async () => {
// try中放可能存在错误的代码
try {
const result1 = await p1
console.log('成功了', result1);
const result2 = await p2
console.log('成功了', result2);
const result3 = await p3
console.log('成功了', result3);
} catch (error) {
console.log('失败了',error);
}
})()
await的应用(解决链式调用)
//准备好一个 Promise封装好的发送ajax get请求的函数 --sendAjax(url,data)
(async()=>{
try {
// 如果wait右侧的表达式为Promise实例对象,await后的返回值是promise成功的值
const result1 = await sendAjax('https://api.apiopen.top/api/getImages',{page:0})
console.log('第1次请求成功了',result1);
const result2 = await sendAjax('https://api.apiopen.top/api/getImages',{page:0})
console.log('第2次请求成功了',result2);
const result3 = await sendAjax('https://api.apiopen.top/api/getImages',{page:0})
console.log('第3次请求成功了',result3);
} catch (error) {
console.log(error);
}
})()
async与await的规则
- async修饰的函数
函数的返回值为promise对象
Promise实例的结果由async函数执行的返回值决定
- await表达式
await右侧的表达式一般为Promise实例对象, 但也可以是其它的值
(1).如果表达式是Promise实例对象, await后的返回值是promise成功的值
(2).如果表达式是其它值, 直接将此值作为await的返回值
//测试async
async function demo(){
const result = await p1
console.log(result);
}
demo()
await原理
若我们使用async配合await这种写法:
1.表面上不出现任何的回调函数
2.但实际上底层把我们写的代码进行了加工,把回调函数“还原”回来了。
3.最终运行的代码是依然有回调的,只是程序员没有看见。
const p = new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve('a')
},4000)
})
async function demo(){
//程序员“轻松”的写法
const result = await p
console.log(result);
console.log(100);
console.log(200);
}
demo()
console.log(1);
//浏览器翻译后的代码
/*
p.then(
result => {
console.log(result);
console.log(100);
console.log(200);
},
)
*/
宏队列与微队列
宏队列:[宏任务1,宏任务2…..] –如主线程上定时器就是宏任务
微队列:[微任务1,微任务2…..] –promise实例对象的then方法内就是微任务
规则:每次要执行宏队列里的一个任务之前,先看微队列里是否有待执行的微任务
1.如果有,先执行微任务
2.如果没有,按照宏队列里任务的顺序,依次执行
//代码一
setTimeout(()=>{
console.log('timeout')
},0)
Promise.resolve(1).then(
value => console.log('成功1',value)
)
Promise.resolve(2).then(
value => console.log('成功2',value)
)
console.log('主线程')
// 控制台结果
主线程
成功1 1
成功2 2
timeout
//代码二
setTimeout(()=>{
console.log('timeout1')
})
setTimeout(()=>{
console.log('timeout2')
})
Promise.resolve(1).then(
value => console.log('成功1',value)
)
Promise.resolve(2).then(
value => console.log('失败2',value)
)
// 控制台结果
成功1 1
失败2 2
timeout1
timeout2
//代码三
setTimeout(()=>{
console.log('timeout1')
Promise.resolve(5).then(
value => console.log('成功了5')
)
})
setTimeout(()=>{
console.log('timeout2')
})
Promise.resolve(3).then(
value => console.log('成功了3')
)
Promise.resolve(4).then(
value => console.log('成功了4')
)
// 控制台结果
成功了3
成功了4
timeout1
成功了5
timeout2
经典面试题
典型的先指定回调后改变状态
以下代码的执行流程:
首先可以判断是先指定回调再改变状态
主线程上的代码在飞速运行,new Promise后瞬间开始执行代码。然后执行器函数excuter是同步的回调函数,由于同步的回调函数不会往回调队列里边进,主线程上直接执行这些代码,瞬间开启一个定时器,那么setTimeout所指定的回调不是马上推入队列(除非到点了),而是放到浏览器的定时器管理模块,到点后定时器管理模块把setTimeout所指定的回调再推向宏队列,当主线程任务完成后才执行setTimeout所指定的回调。
再来看主线程,new Promise在主线程上使用后,执行器函数也是在主线程上执行,定时器setTimeout内置函数也是在主线程上飞速的执行完毕(而最重要的是setTimeout其里边的函数体需要到点后再推向队列),然后主线程开始执行p.then(),这其中有成功和失败的两个函数参数,是挂在实例自身上了。可以理解为只要是Promise实例对象,那么一出生身上就有个类似 P.list = []的结构,于是这里把成功和失败的回调推向这个里边(不是推向队列,因为还不知到上方状态的结果,除非是知道状态才去调用成功/失败的回调)一旦setTimeout内函数体从宏队列拉出执行,才发现状态是否成功。然后去自身找到之前缓存的成功/失败的回调。
const p = new Promise((resolve,reject)=>{
setTimeout(()=>{
// 函数体
resolve('ok')
},1000)
})
p.then(
(value)=>{console.log(value);},
(reason)=>{console.log(reason);}
)
典型的先改状态后指定回调
/* 典型的先改状态后指定回调
这个Promise实例对象一出生瞬间变为成功的状态,然后指定回调。即成功后直接把成功的回调推向了微队列。
(记住要执行的时候才进队列,等着主线程将它勾出来去执行)
*/
const p = new Promise((resolve,reject)=>{
resolve('ok')
})
p.then(
(value)=>{console.log(value);},
(reason)=>{console.log(reason);}
)
面试题
判断出控制台的输出结果?
注意:分析方法的注释是按照1~9顺序排列
// 1、立马反应setTimeout立即调用,但是不能进主线程而是进宏队列 宏队列保存 0
setTimeout(()=>{
console.log('0');
},0)
// 2、当主线程执行到此时,控制台输出 1,并且new一个Promise 判断出它是一个同步的回调 立刻马上输出1 且瞬间状态切换为成功
new Promise((resolve,reject)=> {
console.log('1');
resolve()
// 3、状态成功后执行 .then()方法里的回调,但是!!这是属于异步回调,需要先推入微队列 微队列保存 2
}).then(()=>{
/*
6、主线程分析完后,接着开始分析微队列
此时将2输出到控制台,此刻控制台为 1、7、2 此时微队列抹去 2
又 new了一个Promise,执行器在主线程运行马上输出3,同时resolve()将状态变为成功了 此刻控制台为 1、7、2、3
状态成功后,在.then()方法里有一个为其指定得成功得回调 ,!!注意这个回调也先得推入微队列,即把4所在 回调推入微队列.此时队列保存8、4
下一个.then()也是为左侧.then()返回得新Promise实例对象指定得回调,但是前一个实例的状态是由4所在 的回调来定义,而4目前还未执行,所以5就挂在了自身上不推入队列
此刻当前花括号内代码执行完毕,程序默认给Promise返回的值为undefined
*/
console.log('2');
new Promise((resolve,reject)=> {
console.log('3');
resolve()
}).then(()=>{
/*
9、接着将4所在的回调拉入主线程执行 输出4 此刻控制台为 1、7、2、3、8、4
如果4所在的回调执行了,那么就说明所在的.then的状态就敲定了,返回undefined
因为.then()左侧有了结果,就把 5 推入微队列 ,此时微队列保存6、5
接着将6(所在的回调)拿出队列,推入主线程,此刻控制台为 1、7、2、3、8、4、6
接着将5(所在的回调)拿出队列,推入主线程,此刻控制台为 1、7、2、3、8、4、6、5
最后 把宏队列的0(所在的回到)推入主线程,此刻控制台为 1、7、2、3、8、4、6、5、0
*/
console.log('4');
}).then(()=>{
console.log('5');
})
/*
4、这里的.then()不会推入队列,是因为它左侧的Promise实例还没有执行没有返回出结果,这里当然不能执 行,而是暂存放到左侧Promise实例身上
*/
}).then(()=>{
/*
7、因为.then左侧返回undefined,所以新Promise实例状态为成功, 成功的value为undefined
因为.then()左侧有了结果,就把 6 推入微队列 ,此时队列保存8、4、6
*/
console.log('6');
})
/*
5、又 new了一次Promise 此时控制台先输出 1、7 ,因为直接改变状态为成功,意味着要去调用后边 的.then()方法,当然得先把.then()方法推向微队列 此时队列保存 2 8
*/
new Promise((resolve,reject)=>{
console.log('7')
resolve()
}).then(()=>{
// 8、接着将8从队列拉出,此刻控制台为 1、7、2、3、8 此时队列保存4、6
console.log('8');
})
包管理器的对比:
仅用cnpm的仓库地址
使用yarn的命令
偶尔yarn网络不好,使用npm