目录
前言
JS中或node中,都大量的使用了回调函数进行异步操作,而异步操作什么时候返回结果是不可控的,如果我们希望几个异步请求按照顺序来执行,那么就需要将这些异步操作嵌套起来,嵌套的层数特别多,就会形成回调地狱 或者叫做 横向金字塔
如果按照顺序读取的文件非常多,那么嵌套的代码将会多的可怕,可读性差,难维护
一、promise说明概述
-
Promise是一个构造函数(new Promise),它代表一个异步操作,可以解决回调地狱的问题
-
从语法上说,Promise 是一个对象,使用的使用需要
new
-
Promise构造函数中有一个实参,这个实参是一个函数,这个实参函数会立即调用,new时会返回promise实例
-
实例身上有一个方法then(在原型上),then方法中有两个回调函数
-
第一个回调函数代表成功,这个成功的函数会赋值给resolve,第二个回调函数代表失败,这个失败的函数会赋值给reject
-
有时then()失败的回调函数可以不写,可以用catch捕获错误
-
Promise.prototype有then方法 catch方法,还有finally方法
-
比传统的解决方案(回调函数和事件)更合理和更强大
-
Promise可以理解为一个容器,里面可以编写异步程序的代码
-
Promise是“承诺”的意思,实例中,它里面的异步操作就相当于一个承诺,而承诺就会有两种结果,要么完成了承诺的内容,要么失败
-
使用Promise,分为两大部分,首先是有一个承诺(异步操作),然后再兑现结果
二、使用promise分为两大部分
const fs = require('fs');
// promise 承诺
// 使用Promise分为两大部分
// 1. 定义一个承诺
let p = new Promise((resolve, reject) => {
// resolve -- 解决,完成了; 是一个函数
// reject -- 拒绝,失败了; 是一个函数
// 异步操作的代码,它就是一个承诺
fs.readFile('./a.txt', 'utf-8', (err, data) => {
if (err) {
reject(err);
} else {
resolve(data.length);
}
});
});
// 2. 兑现承诺
// p.then(
// (data) => {}, // 函数类似的参数,用于获取承诺成功后的数据
// (err) => {} // 函数类型的参数,用于或承诺失败后的错误信息
// );
p.then(
(data) => {
console.log(data);
},
(err) => {
console.log(err);
}
);
三、promise的三种状态
-
最初状态:pending,等待中,此时promise的结果为 undefined;
-
当 resolve(value) 调用时,达到最终状态之一:fulfilled,(成功的)完成,此时可以获取结果value
-
当 reject(error) 调用时,达到最终状态之一:rejected,失败,此时可以获取错误信息 error
达到最终的 fulfilled 或 rejected 的时候,promise的状态就不会再改变了
四、同步和异步问题
new Promise是同步执行,获取结果时(调用 resolve 触发 then方法时)是异步的
console.log(1);
new Promise((resolve, reject) => {
console.log(2);
resolve();
console.log(3);
}).then(res => {
console.log(4);
})
console.log(5);
// 输出顺序: 1 2 3 5 4 ,因为只有 .then() 是异步的
五、then方法的链式调用
概述说明
前一个then里面返回的字符串,会被下一个then方法接收到。但是没有意义
但是前一个then里面返回的Promise对象,并且调用resolve的时候传递了数据,数据会被下一个then接收到
注意:前一个then里面如果没有调用resolve,则后续的then不会接收到任何值
实现原理
Promise 也还是使用回调函数,只是把回调封装在内部,一直通过 then 方法的链式调用,使得多层的回调嵌套看起来变成了同一层的,书写上以及理解上会更直观和简洁一些
封装按顺序异步读取文件的函数
//封装一个函数读取文件
import fs from 'fs'
function read(fpath) {
let p = new Promise(function(resolve, reject) {
fs.readFile(fpath, 'utf-8', (err, dataStr) => {
if(err) return reject(err)
resolve(dataStr)
})
})
return p
}
read('./files/1.txt').then(function(res1) {
console.log(res1)
return read('./files/2.txt')
}).then(function(res2) {
console.log(res2)
return read('./files/3.txt')
}).then(function(res3) {
console.log(res3)
}).catch(function(err){
//读取错误
console.log(err.message)
})
catch方法可以统一获取错误信息
六、promise的静态方法
Promise.all()
Promise.all() 方法会发起并行的 Promise 异步操作,等所有的异步操作全部结束后才会执行下一步的.then操作(等待机制),数组中 Promise 实例的顺序,就是最终结果的顺序
//封装一个函数读取文件
import fs from 'fs'
function read(fpath) {
let p = new Promise(function(resolve, reject) {
fs.readFile(fpath, 'utf-8', (err, dataStr) => {
if(err) return reject(err)
resolve(dataStr)
})
})
return p
}
let p1 = read('./files/1.txt')
let p2 = read('./files/2.txt')
let p3 = read('./files/3.txt')
// let promiseArr = [p1, p2, p3]
Promise.all([p1, p2, p3]).then(res => {
console.log(res);
})
Promise.race()
Promise.race() 方法会发起并行的 Promise 异步操作,只要任何一个异步操作完成,就立即执行下一 步的.then 操作(赛跑机制),有一个promise异步操作执行完毕时,then方法中的结果时最先获取数据的结果
七、async/await 简化Promise异步操作
async/await概述说明
一直有人提出各种各样的方案,试图解决 异步操作,从最早的回调函数,到 Promise 对象,再到 Generator 函数,每次都有所改进,但又让人觉得不彻底。它们都有额外的复杂性,都需要理解抽象的底层运行机制
ES2017提供了async和await关键字。await和async关键词能够将异步请求的结果以返回值的方式返回给我们
-
async 用于修饰一个 function,如果一个函数声明前用了async,这个函数可以称为异步函数
-
async 修饰的函数,总是返回一个 Promise 对象
-
没有return时,空promise没有太多实质意义
-
return一个非promise,将数据包装成了promise
-
return promise, 返回的就是该promise。
-
-
await 只能出现在 async 函数内
-
await 让 JS 引擎等待直到promise完成并返回结果
-
语法:let value = await promise对象; // 要先等待promise对象执行完毕,才能得到结果
-
由于await需要等待promise执行完毕,所以await会暂停函数的执行,但不会影响其他异步任务
-
-
对于错误处理,可以选择在async函数后面使用
.catch()
或 在promise对象后使用.catch()
简化代码
async function getFile(){
const p1 = await read('./files/1.txt')
const p2 = await read('./files/2.txt')
const p3 = await read('./files/3.txt')
console.log(p1,p2,p3);
}
getFile()
async/await的使用注意事项
① 如果在 function 中使用了 await,则 function 必须被 async 修饰
② 在 async 方法中,第一个 await 之前的代码会同步执行,await 之后的代码会异步执行
console.log('a');
async function getFile(){
console.log("b");
const p1 = await read('./files/1.txt')
const p2 = await read('./files/2.txt')
const p3 = await read('./files/3.txt')
console.log("c");
console.log(p1,p2,p3);
console.log("d");
}
console.log("e");
getFile()
console.log("f");
//结果: a e b f c 111 222 333 d
八、宏任务和微任务
JavaScript 是一门单线程执行的编程语言。同一时间只能做一件事情
eventloop
为了防止某个耗时任务导致程序假死的问题,JavaScript 把待执行的任务分为了两类:
① 同步任务(synchronous)
-
又叫做非耗时任务,指的是在主线程上排队执行的那些任务
-
只有前一个任务执行完毕,才能执行后一个任务
② 异步任务(asynchronous)
-
又叫做耗时任务,异步任务由 JavaScript 委托给宿主环境进行执行
-
当异步任务执行完成后,会通知 JavaScript 主线程执行异步任务的回调函数
先执行同步任务,只有所有的同步任务执行完毕时,才执行执行异步任务
//代码
console.log(1);
setTimeout(function(){
console.log(2);
},1000)
console.log(3);
//结果: 1 3 2
for(var i = 0; i<5; i++){
setTimeout(function(){
console.log(i);
},i*1000)
}
//结果: 5个5 全局变量i=5
for(let i = 0; i<5; i++){
setTimeout(function(){
console.log(i);
},i*1000)
}
//结果: 0 1 2 3 4 块级作用域
宏任务和微任务说明
① 宏任务(macrotask)
-
异步 Ajax 请求、
-
setTimeout、setInterval、
-
文件操作
-
其它宏任务
② 微任务(microtask)
-
Promise.then、.catch 和 .finally
-
process.nextTick
-
其它微任务
宏任务和微任务的执行顺序
先主线程 再异步任务,异步任务
先微后宏(注意如果宏任务里面有微任务,这里的微任务不会在当前宏任务之前执行)
经典面试题
console.log(1)
setTimeout(function(){
console.log(2)
new Promise(function(resolve){
console.log(3)
resolve()
}).then(function(){
console.log(4)
})
},1000)
new Promise(function(resolve){
console.log(5)
resolve()
}).then(function(){
console.log(6)
})
setTimeout(function(){
console.log(7)
new Promise(function(resolve){
console.log(8)
resolve()
}).then(function(){
console.log(9)
})
},500)
//结果: 1 5 6 7 8 9 2 3 4