一、Promise是什么?
1.1 概念
抽象表达
-
Promise是一门新的技术(ES6规范)
-
Promise是JS中进行
异步编程
的新解决方案(旧方案使用普通的回调函数)
具体表达
-
从语法上来说,
Promise是一个构造函数
,可以进行对象实例化; -
从功能上来说:Promise对象用来
封装一个异步操作
并可以获取
其成功/失败的结果值
【异步操作编程包含: fs文件操作
、数据库操作
、定时器
】
1.2 Promise的状态改变
1.2.1 promise的状态是什么?
promise状态
是Promise实例对象
中的一个属性【PromiseState】
状态的属性值包含三种:
-
pending
未决定(初始化的默认值) -
resolved/fullfilled
成功 -
rejected
失败
说明
:
promise状态的变化只有2种,且一个promise对象只能改变一次;pending变为resolved或者pending变为rejected。 无论成功还是失败,都会有一个结果数据。 成功的结果一般称为value , 失败的结果数据一般称为reason。
1.2.2 Promise对象的值:
实例对象中的另一个属性【PromiseResult】
保存着异步任务成功/失败的结果
谁可以修改PromiseResult的值呢:
-
resolve
-
reject
1.3 Promise的基本流程
1.4 Promise的优势
-
指定回调函数的方式
更加灵活
-
支持链式调用,可以解决
回调地狱
问题
1.4.1 什么是回调地狱?
回调函数嵌套调用
,外部函数异步执行结果是嵌套的回调执行的条件。
ajax1(url, () => {
ajax2(url, () => {
ajax3(url, () => {
doSomething()
})
})
})
1.4.2 回调地狱的缺点?
-
不便于阅读
-
不便于异常处理
二、如何使用Promise?
2.1 API
2.1.1 Promise构造函数:Promise(excutor){}
-
executor函数: 执行器【Promise对象的参数】
(resolve, reject) => {}
-
resolve函数: 内部定义成功时我们调用的函数
value => {}
-
reject函数: 内部定义失败时我们调用的函数
reason =>{}
说明: exector会在Promise内部立即同步调用
,异步操作在执行器中执行
。
const p = new Promise((resolve, reject) => {
console.log(123)
})
console.log(456)
//先打印123 后打印456
2.1.2 Promise.prototype.then方法:(onResolved, onRejected) => {}
-
onResolved函数:成功的回调函数
(value) => {}
-
onRejected函数:失败的回调函数
(reson) => {}
说明: 指定用于得到成功value的成功回调和用于得到失败reson的失败回调返回一个新的promise对象
。
2.1.3 Promise.prototype.catch方法:(OnRejected)=>{}
- onRejected函数:失败的回调函数
(reson)=>{}
const p = new Promise((resolve, reject) => {
reject('返回错误')
})
p.catch(reason =>{
console.log(reason)
})
2.1.4 Promise.prototype.finnaly方法:(finnaly) =>{}
finally()
方法用于指定不管 Promise
对象最后状态如何,都会执行的操作.
promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});
上面代码中,不管promise
最后的状态,在执行完then
或catch
指定的回调函数以后,都会执行finally
方法指定的回调函数。
finally本质
上是then方法的特例
.
2.1.5 Promise.resolve方法:(value)=> {}
-
属于
Promise函数对象
并不属于实例对象 -
作用: 为了快速得到一个
Promise对象
,而且可以封装一个值,将这个值转化为Promise对象; -
value:
成功
的数据或者promise对象 -
说明:返回一个
成功
的promise对象
- 如果传入的对象为非Promise类型的对象,则返回的结果为成功promise对象
- 如果传入的参数为Promise对象,则参数的结果决定了resolve的结果 传入非promise类型对象
const p1 = Promise.resolve(521)
console.log(p1)
传入一个promise对象
const p1 = Promise.resolve(new Promise((resolve, rejcte) => {
rejcte('Error')
}))
console.log(p1)
2.1.6 Promise.reject方法:(reason) => {}
-
reason: 失败的原因
-
作用: 快速返回一个失败的
Promise对象
-
说明: 返回一个失败的
promise
对象
const p2 = Promise.reject(521);
console.log(p2)
const p1 = Promise.reject(new Promise((resolve, rejcte) => {
resolve('hahha')
}))
console.log(p1)
2.1.7 Promise.all方法: (promise) =>{}
-
promise:
包含N个promise的数组
-
说明: 返回一个新的Promise,
只有所有的promise都成功才执行,只要有一个失败了就直接失败
。
const p1 = new Promise((resolve, reject) => {
resolve('OK')
})
const p2 = Promise.resolve('SUCCESS');
const p3 = Promise.resolve('Oh Yeah');
const res = Promise.all([p1,p2,p3]);
console.log(res)
如果有一个失败了,那么返回一个失败的promise
对象,失败的结果值就是失败的那个promise
的失败结果值。
const p1 = new Promise((resolve, reject) => {
resolve('OK')
})
const p2 = Promise.reject('Error');
const p3 = Promise.resolve('Oh Yeah');
const res = Promise.all([p1,p2,p3]);
console.log(res)
2.1.8 Promise.race方法:(promises) =>{}
-
promises:
包含n个promise的数组
-
谁先改变状态,谁先决定race返回的promise状态
-
说明: 返回N个Promise的数组,第一个完成的promise的结果状态就是最终的结果状态。
const p1 = new Promise((resolve, reject) => {
resolve('OK')
})
const p2 = Promise.reject('Error');
const p3 = Promise.resolve('Oh Yeah');
const res = Promise.race([p1,p2,p3]);
console.log(res)
2.2 Promise的几个关键问题
2.2.1 如何改变Promise的状态?
-
resolve(value):如果当前时pending就会变成
resolved
; -
rejected: 如果当前时pending就会变成
rejected
; -
抛出异常: 如果当前状态时pending就会变成
rejected
;
const p = new Promise((resolve,reject) => {
//1. resolve函数
// resolve('haha') pending => fulfilled(resolved)
//2. reject函数
// reject('Error') pending => rejected
//3. 抛出错误
throw '报错了' // pending => rejected
})
console.log(p)
2.2.2 一个promise指定多个成功或者失败回调函数,都会调用吗?
当promise
改变为对应状态时都会调用,如果状态不改变都不执行。
const p = new Promise((resolve,reject) => {
resolve('我来了')
})
p.then(value => {
console.log(value+ '第一次调用')
})
p.then(value => {
console.log(value+ '第二次调用')
})
2.2.3 改变promise状态和指定回调函数谁先谁后?(resolve()和then()哪个先执行)
下面以resolve()改变状态和then()回调函数为例:
当执行器函数
是同步
时:先改变状态【resolve()】,再执行回调函数【then()】
const p = new Promise((resolve,reject) => {
resolve('我来了')
})
p.then(value => {
console.log(value)
})
当执行器函数
是异步
时:先执行回调函数【then()】,再改变状态【resolve()】
const p2 = new Promise((resolve,reject) => {
setTimeout(() => {
resolve('我来了')
}, 1000);
})
p2.then(value => {
console.log(value)
})
如何先改状态再指定回调?
-
在执行器中直接调用
resolve()/reject()
-
延迟更长时间再调用用
then()
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('我来了')
}, 1000);
})
setTimeout(() => {
p2.then(value => {
console.log(value)
})
}, 2000);
什么时候才能得到数据?
-
如果先指定的回调,那当状态发生改变时,回调函数就会调用,得到数据
-
如果先改变状态, 那当致电给回调时,回调函数就会调用,得到数据
2.2.4 promise.then()返回新的promise的结果状态由什么决定?
由then()指定的回调函数执行的结果决定。
-
如果抛出异常,新
promise
变为rejected
,reason
为抛出的异常; -
如果返回的是非
promise
的任意值,新promise
变为resolved
,value
为返回的值; -
如果返回的是另一个新
promise
,此promise
的结果就会为新的promise
的结果;
const p1 = new Promise((resolve, reject) => {
resolve('OK')
})
const res = p1.then( value => {
// 1. 抛出错误
// throw 'Error'
//2. 返回的结果时非promise类型的对象
// return 521
//3. 返回的结果是一个promise对象,return 的结果就决定了then()的状态
return new Promise((resolve, reject) => {
resolve('success')
})
})
console.log(res)
2.2.5 promise如何串连多个操作任务?
-
promise的then()返回一个
新的promise
,可以开成then()的链式调用; -
通过then的链式调用串连多个异步/同步任务
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('OK')
}, 1000);
})
p1.then(value => {
return new Promise((resolve, reject) => {
resolve('Oh Yeah')
})
}).then(value => {
console.log(value)
})
2.2.6 promise异常传透
-
当使用promise的then链式调用时,可以在最后指定失败的回调
; -
前面任何操作除了异常,都会传到最后失败的回调中处理;
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('OK')
}, 1000);
})
p1.then(value => {
return new Promise((resolve, reject) => {
reject('报错了')
})
}).then(value => {
console.log(11)
}).then(value => {
console.log(22)
}).catch(reason => {
console.log(reason)
})
前面的任何环节,只有有失败的promise
返回,或者抛出异常,都会在最后一个catch()
方法对失败进行处理。
2.2.7 中断promise链?
promise链: 就是用.then().then().then()
形成的链式promise;
-
当使用
promise
的then
链式调用时,在中间中断,不在调用后面的回调函数 -
办法: 再回调函数中返回一个
pending
状态的promise
对象 后面的then()
方法都不会执行,因为promise
的状态没有改变
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('OK')
}, 1000);
})
p1.then(value => {
return new Promise((resolve, reject) => {
console.log(11)
})
}).then(value => {
//有且只有一个方法
//后面的then()方法都不会执行,因为promise的状态没有改变
return new Promise(() => {})
}).then(value => {
console.log(22)
}).catch(reason => {
console.log(reason)
})
// 11
三、 async与await
3.1 async
async 函数是什么?
它就是 Generator 函数的语法糖
。
前文有一个 Generator 函数,依次读取两个文件。
var fs = require('fs');
var readFile = function (fileName){
return new Promise(function (resolve, reject){
fs.readFile(fileName, function(error, data){
if (error) reject(error);
resolve(data);
});
});
};
var gen = function* (){
var f1 = yield readFile('/etc/fstab');
var f2 = yield readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
写成 async 函数,就是下面这样:
var asyncReadFile = async function (){
var f1 = await readFile('/etc/fstab');
var f2 = await readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
一比较就会发现,async函数
就是将Generator函数
的星号(*
) 换成async
,将yield
替换成await
,仅此而已。
- 函数的返回值是
promise对象
如果一个async函数
的返回值看起来不是promise
,那么它将会被隐式
地包装在一个promise
中。 promsie对象的结果由 async函数执行的返回值决定
async function foo(){
// return 5
// 上面写法等价于下面的写法
return Promise.resolve(5)
}
console.log(foo())
3.2 await
await
操作符用于等待
一个Promise对象
,它只能在异步函数async function
中使用。 await表达式会暂停当前async function
的执行,等待Promise
处理完成。
-
若
Promise
正常处理(fulfilled
),其回调的resolve
函数参数作为await
表达式的值,继续执行async function
; -
若
Promise
处理异常rejected
,await
表达式会把Promise的异常原因抛出
; -
如果await操作父后的表达式的值
不是一个Promise
,则返回该值本身
; -
await右侧的表达式一般为
promise对象
,但是也可以是其他值;
3.3 使用Async与Await的优点?
async
表示函数里有异步操作
,await
表示紧跟在后面的表达式需要等待结果。
-
Async和Await关键字让我们可以用更简洁的方式写出基于Promise的异步执行行为,而无需刻意地链式调用Promise.
-
用同步的编写方式写异步代码
-
更方便调试代码
-
解决回调地狱
一个不含await表达式的async函数是会同步运行的。然而,如果函数体内有一个await表达式,async函数就一定会异步执行。
3.4 使用Async与Await的注意事项
await
命令后面的Promise
对象,运行结果可能是rejected
,所以最好把await
命令放在try...catch
代码块中。
async function myFunction() {
try {
await somethingThatReturnsAPromise();
} catch (err) {
console.log(err);
}
}
// 另一种写法
async function myFunction() {
await somethingThatReturnsAPromise()
.catch(function (err) {
console.log(err);
});
}
- 多个
await
命令后面的异步操作
,如果不存在继发关系,最好让他们同时触发
,这样就会缩短
程序的执行时间。
// 写法一
let [foo, bar] = await Promise.all([getFoo(), getBar()]);
// 写法二
let fooPromise = getFoo();
let barPromise = getBar();
let foo = await fooPromise;
let bar = await barPromise;
await
命令只能用在async
函数之中,如果用在普通函数,就会报错。
async function dbFuc(db) {
let docs = [{}, {}, {}];
// 报错
docs.forEach(function (doc) {
await db.post(doc);
});
}
上面代码会报错,因为await
用在普通函数之中了。但是,如果将forEach
方法的参数改成async
函数,也有问题。
async function dbFuc(db) {
let docs = [{}, {}, {}];
for (let doc of docs) {
await db.post(doc);
}
}
3.5 举例
function sendAjax(method, url) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
// xhr.responseType = 'json';
xhr.open(method, url);
xhr.send();
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300) {
resolve(xhr.response)
} else {
reject(xhr.code)
}
}
}
})
}
//promise的then()调用
sendAjax("GET", './data/content.txt').then(data => {
console.log(data)
}, code => {
console.log(code)
})
// async await
async function getData(){
try {
const data = await sendAjax("GET", './data/content.txt');
console.log(data)
} catch (error) {
console.log(error)
}
}
五、JS异步之微队列与宏队列
5.1 原理图
一开始整个脚本作为一个宏任务执行
执行过程中同步代码直接执行,红任务进入宏任务队列,微任务进入微任务队列
当前宏任务执行完出队,检查微任务列表,有则一次执行,直到全部执行完
执行浏览器UI线程的渲染工作
5.2 举例说明
setTimeout(() => {
console.log('timeout callback1()')
Promise.resolve(3).then(
value => {
console.log('Promise3' +'***'+ value)
}
)
}, 0);
setTimeout(() => {
console.log('timeout callback2()')
}, 0);
Promise.resolve(1).then(
value => {
console.log('Promise1' +'***'+ value)
}
)
Promise.resolve(2).then(
value => {
console.log('Promise2' +'****'+ value)
}
)
JS中用来存储待执行回调函数的队列包含2个不同特定的队列。
5.2 总结
宏队列: 用来保存待执行的宏任务(回调),比如: 定时器回调
/DOM事件回调
/Ajax回调
微队列: 用来保存待执行的微任务(回调),比如: promise的回调
/MutationObserver的回调
宏任务: script、 定时器、I/O、 UIrander
微任务: MutationObserver、Promise.then()或catch()、Promise为基础开发的其他技术: 比如fetchApi 、 V8的垃圾回收过程、 Node独有的Promise.nextTick.
JS执行时会区别2个队列:
-
JS引擎必须执行所有的初始化同步代码
-
每次准备取出第一个宏任务执行前,都要将所有的微任务一个一个取出来执行
同步 => 微任务 => 宏任务
5.3 Promise相关面试题:
面试题一:
setTimeout(() => {
console.log(1)
}, 0);
Promise.resolve().then(() => {
console.log(2)
})
Promise.resolve().then(() => {
console.log(4)
})
console.log(3)
// 3 2 4 1
面试题二:
setTimeout(() => {
console.log(1)
}, 0);
new Promise((resolve) => {
console.log(2) // 同步
resolve()
}).then(() => {
console.log(3)
}).then(() => {
console.log(4)
})
console.log(5)
//2 5 3 4 1
面试题三
const first = () => (new Promise((resolve, reject) => {
console.log(3)
let p = new Promise((resolve, reject) => {
console.log(7)
setTimeout(() => {
console.log(6)
resolve(6)
}, 0);
resolve(1)
})
resolve(2)
p.then(arg => {
console.log(arg)
})
}))
first().then(arg => {
console.log(arg)
})
console.log(4)
// 3 7 4 1 2 6
解析:
先执行first()里面的同步,然后打印4,然后执行first里面的微任务,最后是宏任务。
面试题四
setTimeout(() => {
console.log(0)
}, 0);
new Promise((resolve, reject) => {
console.log(1)
resolve()
}).then(() => { // 1
console.log(2)
new Promise((resolve, reject) => {
console.log(3)
resolve()
}).then(() => { //3
console.log(4)
}).then(() => { //4
console.log(5)
})
}).then(() => { // 2
console.log(6)
})
new Promise((resolve, reject) => {
console.log(7)
resolve()
}).then(() => {
console.log(8) //1
})