文章目录
异步编程
众所周知,Javascript语言的执行环境是"单线程"(single thread)。
所谓"单线程",就是指一次只能完成一件任务。如果有多个任务,就必须排队,前面一个任务完成,才会执行下一个任务。所以,在程序执行的每一步,都可以推断出程序的状态,这是因为后面的指令总是在前面的指令完成后才会执行,即同步行为。
相对的,异步行为是必要的,因为强制进程等待一个长时间的操作通常是不可行的(同步操作则必须等),比如向远程服务器发送请求并等待响应,就会出现长时间的等待。当然,异步代码不容易推断,设计一个可以推断异步代码实况状态的系统是很难的,JavaScript在实现这样一个系统的过程中也经历的几次迭代。
以往的异步编程模式
在早期的Javascript中,只支持定义回调函数来表明异步操作完成。
function double(value){
setTimeOut(()=>{
setTimeOut(console.log,0,value)
},1000);
}
这段代码没有什么神秘的,之所以说他是一个异步函数,是因为,1000ms之后,JavaScript会把会把回调函数推到自己的队列上等待执行。推到队列之后,回调什么时候出列被执行就是未知的了。另外,double
函数在setTimeOut
成功调度异步操作之后会立即退出。
异步返回值
假设setTimeOut操作会返回一个有用的值,可以给异步操作提供一个回调,这个回调中包含要使用异步返回值的代码(作为回调的参数)。
function double(value,callback){
setTimeOut(()=>callback(value),1000);
}
这里的setTimeOut调用告诉JavaScript运行时在1000ms之后把一个函数推到消息队列里。如果异步返值又依赖另一个异步返回值,就会出现嵌套(会回调地狱),非常难以维护。
Promise
ECMAScript 6 新增的引用类型Promise
其实是一个构造函数,可以通过new
操作符来实例化。他自己身上有all
、reject
、resolve
等方法,原型上有then
、catch
等方法。
大致介绍了promise出现的背景,接下来就回到写这篇文章的初衷🥧~
用一些小demo来学习promise~
Promise基础题
题目一
const promise1 = new Promise((resolve, reject) => {
console.log('promise1')
})
console.log('1', promise1);
- 首先遇到
new Promise
,执行该构造函数中的同步代码console.log(‘promise1’ )
; - 接下来,执行同步代码,打印1,此时Promise1的状态为
pending
待定中;
结果:
'promise1'
'1' Promise{
<pending>}
题目二
const promise = new Promise((resolve, reject) => {
console.log(1);
resolve('success')
console.log(2);
});
promise.then(() => {
console.log(3);
});
console.log(4);
- 首先遇到
new Promise
,执行同步代码打印1; - 遇到
resolve('success')
,将promise的状态改为resolve
,并将值(success)保存下来; - 执行同步代码打印出2;
- 继续向下,promise.then是微任务,所以将promise.then加入微任务队列,此时promise状态已经改变为
resolved
; - 执行同步代码打印4;
- 至此,同步任务执行完毕。本轮事件循环(整段script代码)的宏任务执行完毕,开始执行微任务队列的
promise.then
。
结果:1 2 4 3
题目三
const promise = new Promise((resolve, reject) => {
console.log(1);
console.log(2);
});
promise.then(() => {
console.log(3);
});
console.log(4);
- 和第二题类似,但是promise没有状态,所以
promise.then( )
不会执行。所以,执行promise.then
的前提的该promise的状态已经变为成功或拒绝。
结果:1 2 4
题目四
const promise1 = new Promise((resolve, reject) => {
console.log('promise1')
resolve('resolve1')
})
const promise2 = promise1.then(res => {
console.log(res)
})
console.log('1', promise1);
console.log('2', promise2);
- 首先遇到new Promise,执行同步代码打印
promise1
; - 遇到resolve,将promise1的状态修改为
resolved
,并将值保存下来; promise.then( )
放入微任务队列;- 执行同步代码,promise1的状态为
resolve
,promise2的状态为pending
; - 本轮宏任务执行完毕,开始执行微任务队列,此时状态为
resolve
。
结果:
'promise1'
'1' Promise{
<resolved>: 'resolve1'}
'2' Promise{
<pending>}
'resolve1'
题目五
const fn = () => (new Promise((resolve, reject) => {
console.log(1);
resolve('success')
}))
fn().then(res => {
console.log(res)
})
console.log('start')
- 首先执行同步代码打印1;
- 将promise状态改为
resolve
,并保存值; fn().then
放入微任务队列;- 执行同步代码start;
- 执行微任务,res为上一层的promise对象,值为success。
结果:
1
'start'
'success'
promise结合setTimeout
题目一
console.log('start')
setTimeout(() => {
console.log('time')
})
Promise.resolve().then(() => {
console.log('resolve')
})
console.log('end')
- 整段js代码作为一个宏任务,同步代码直接压入执行栈;
setTimeout
放入宏任务队列;promise.then()
放入微任务队列;- 执行同步代码打印end;
- 本轮宏任务执行完毕。开始执行微任务队列中的
promise.then
; - 开启下一轮事件循环,执行宏任务setTimeOut。
结果:
'start'
'end'
'resolve'
'time'
题目二
const promise = new Promise((resolve, reject) => {
console.log(1);
setTimeout(() => {
console.log("timerStart");
resolve("success");
console.log("timerEnd");
}, 0);
console.log(2);
});
promise.then((res) => {
console.log(res);
});
console.log(4);
- 首先遇到
new Promise
,执行同步代码打印1; setTimeOut
放入宏任务队列中; 执行同步代码打印2;promise.then
放入微任务队列;执行同步代码打印4;- 进入第二轮循环,执行宏任务
setTimeOut
,输出timerstart,timerEnd,将promise状态变为resolve,并保存值; - 此时,promise状态为
resolve
,执行console.log同步代码,res为上一层返回的promise
对象。
结果:
1
2
4
"timerStart"
"timerEnd"
"success"
题目三
Promise.resolve().then(() => {
console.log('promise1');
const timer2 = setTimeout(() => {
console.log('timer2')
}, 0)
});
const timer1 = setTimeout(() => {
console.log('timer1')
Promise.resolve().then(() => {
console.log('promise2')
})
}