promise的用法
异步编程的解决方案
首先我们知道,由于 JavaScript 是单线程执行模型,因此必须支持异步编程才能提高运行效率。异步编程的语法目标是让异步过程写起来像同步过程。
举个栗子,有三个文件1.txt 2.txt 3.txt 分别是111 222 333,按顺序读取出数据 111=>222=>333,由于readFile读取文件是异步的,所以按照这个方式会导致顺序不一致
// 读取文件 1.txt
fs.readFile('./files/1.txt','utf-8',(err,dataStr)=>{
if(err) return console.log(err.message) //读取文件 1 失败
console.log(dataStr) //读取文件 1 成功
})
// 读取文件 2.txt
fs.readFile('./files/2.txt','utf-8',(err,dataStr)=>{
if(err) return console.log(err.message) // 读取文件 2 失败
console.log(dataStr) //读取文件 2 成功
})
// 读取文件 3.txt
fs.readFile('./files/3.txt','utf-8',(err,dataStr)=>{
if(err) return console.log(err.message) // 读取文件 3 失败
console.log(dataStr) // 读取文件 3 成功
})
// ---------------------------------------------------------------
// 打印结果为:111 333 222
回调函数
这个时候我们可以用回调函数解决
fs.readFile('./files/1.txt','utf-8',(err,dataStr)=>{
if(err) return console.log(err.message)
console.log(dataStr)
fs.readFile('./files/2.txt','utf-8',(err,dataStr)=>{
if(err) return console.log(err.message)
console.log(dataStr)
fs.readFile('./files/3.txt','utf-8',(err,dataStr)=>{
if(err) return console.log(err.message)
console.log(dataStr)
})
})
})
// 打印结果为:111 222 333
用这种方法确实是可以实现异步编程,但是回调函数最大的问题是容易形成回调地狱,即多个回调函数嵌套,降低代码可读性,增加逻辑的复杂性,容易出错。
为了解决回调地狱的问题,ES6(ECMAScript 2015)中新增了 Promise
的概念。
回调地狱
说promise之前,我们先来说一下到底什么是回调地狱,官方语言是 多层回调函数的相互嵌套,就形成了回调地狱。
那么我们用大白话来说 回调地狱就是因为多个串连的异步操作导致的,串连就是第二个是在第一个成功之后,第三个是在第二个成功之后,最终形成回调函数的嵌套。
举栗说明
比如说现在要发三个异步请求,但是我的条件是第二个请求需要第一个请求成功的一部分数据,第三个请求需要第二个请求成功的一部分数据才能发,我想得到的是最后第三个请求返回出来成功的结果,那这个时候我们如果用回调函数的方式去写,那我的第二个请求的发送是在第一个请求的回调函数里面,那我发第二个请求的又指定了一个回调函数,那就形成了回调函数的嵌套。这就是回调地狱
示例代码如下:
// -----------异步嵌套--------------------------------------------
fs.readFile('./files/1.txt','utf-8',(err,dataStr)=>{
if(err) return console.log(err.message)
console.log(dataStr)
fs.readFile('./files/2.txt','utf-8',(err,dataStr)=>{
if(err) return console.log(err.message)
console.log(dataStr)
fs.readFile('./files/3.txt','utf-8',(err,dataStr)=>{
if(err) return console.log(err.message)
console.log(dataStr)
})
})
})
promise
为解决回调函数的不足,JS中进行异步编程的新解决方案 promise
首先来了解一下什么是 promise
promise的概念
1.Promise 是一个构造函数
我们可以创建 Promise 的实例 const p = new Promise()
每一个new 出来的 Promise 实例对象,都代表一个异步操作
2.Promise 是一个构造函数的实参也是一个函数
当new Promise时,这个实参函数会立即执行
3.Promise.prototype 上包含一个 .then() 方法
每一次 new Promise() 构造函数得到的实例对象,
都可以通过原型链的方式访问到 .then() 方法,例如 p.then()
4.then() 方法用来预先指定成功和失败的回调函数
p.then(成功的回调函数,失败的回调函数)
p.then(result => { }, error => { })
调用 .then() 方法时,成功的回调函数是必选的、失败的回调函数是可选的
5.p.then(成功的回调函数,失败的回调函数)
成功的回调函数会赋值 给 构造函数实参函数的 resolve
失败的回调函数会赋值 给 构造函数实参函数的 reject
图例说明
首先,创建一个Promise实例对象,通过new Promise实现,new Promise时,会返回Promise实例p。在new的时候指定了一个执行器函数,异步操作封装在执行器函数里面,当前是pending状态,promise 有三种状态:Pending(初始状态)、Resolved(成功,又称 Fulfilled)、Rejected(失败)。
第二步执行异步操作,成功了执行resolve,此时promise状态为resolved状态,失败了执行rejecte,此时promise状态为Rejected状态,状态只能是三种中的一种,不能同时有两种状态。
第三步实例p身上有一个方法then,then这个方法在原型上,then方法中有两个回调函数: 第一个回调函数代表成功,第二个回调函数代表失败 成功了会调用成功的回调函数,这个成功的函数会赋值给resolve。失败了会调用失败的回调函数,这个成功的函数会赋值给resolve 。
有时then()失败的回调函数可以不写,可以用.catch用来捕获错误
最后会返回一个新的promise对象。
如何声明一个Promise
new Promise(function(resolve, reject){ })
如果想让Promise成功执行下去,需要执行resolve,如果让它失败执行下去,需要执行reject
new Promise(function(resolve, reject) {
resolve('success') // 成功执行
}).then(result => {
alert(result)
})
new Promise(function(resolve, reject) {
reject('fail') // 成功执行
}).then(result => {
alert(result)
}).catch(error => {
alert(error)
})
如果想终止在某个执行链的位置,可以用Promise.reject(new Error())
new Promise(function(resolve, reject) {
resolve(1)
}).then(result => {
return result + 1
}).then(result => {
return result + 1
}).then(result => {
return Promise.reject(new Error(result + '失败'))
// return result + 1
}).then(result => {
return result + 1
}).catch(error => {
alert(error)
})
用promise创建具体的异步操作
基于 Promise 按顺序读取文件的内容
//具体实现
import fs from 'fs'
const p = new Promise(function(resolve,reject){
fs.readFile("./files/11.txt",'utf-8',(err,dataStr)=>{
if(err) return reject(err);
resolve(dataStr)
})
})
p.then(function(data){
console.log(data);
},function(err){
console.log(err.message);
})
获取 .then 的两个实参
通过 .then() 指定的成功和失败的回调函数,可以在 function 的形参中进行接收,示例代码如下:
调用 resolve 和 reject 回调函数
Promise 异步操作的结果,可以调用 resolve 或 reject 回调函数进行处理。示例代码如下:
执行顺序示意图
通过 .catch 捕获错误
在 Promise 的链式操作中如果发生了错误,可以使用 Promise.prototype.catch 方法进行捕获和处理:
import fs from 'fs'
const p = new Promise(function(resolve,reject){
fs.readFile("./files/11.txt",'utf-8',(err,dataStr)=>{
if(err) return reject(err);
resolve(dataStr)
})
})
// catch捕获错误
p.then(function(data){
console.log(data);
}).catch(function(err){
console.log(err.message); //ENOENT: no such file or directory
})
解决异步操作的完整过程
import fs from 'fs'
function read(fatch){
let p = new Promise(function(resolve,reject){
fs.readFile(fatch,'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)
})
总结:promise通过.then方法的链式调用,来避免回调地狱,但是最大问题是代码冗余,语义不清晰。
2.async、await
为了解决 Promise 的问题,async、await 在 ES7 中被提了出来,async、await是基于promise进一步封装的,是异步操作的终极解决方案
基本使用
//具体实现
async 函数名() {
const res1 = await promise实例1
const res2 = await promise实例2
console.log(res1,res2) //按照顺序打印出结果
}
async/await的返回值问题
async声明的函数返回值
永远是promise实例
await后面跟promise实例
//具体实现
// 1.异步函数返回值问题: 永远返回的是一个promise实例
// 1.1 没有return 空promise 没有太多实质意义
async function show(){
}
console.log(show())
// 1.2 return 非promise 将数据包装成了promise
async function show(){
return 3
}
console.log(show())
// 1.3 return promise 返回的就是该promise
async function show(){
return promise实例
}
console.log(show())
使用 async/await 简化 Promise 异步操作的示例代码如下
// async/await 解决按需读取文件
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() //111 222 333
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
async 和 await实际上就是让我们像写同步代码那样去完成异步操作
await 表示强制等待的意思,await关键字的后面要跟一个promise对象,它总是等到该promise对象resolve成功之后执行,并且会返回resolve的结果
async test () {
// await总是会等到 后面的promise执行完resolve
// async /await就是让我们 用同步的方法去写异步
const result = await new Promise(function (resolve, reject) {
setTimeout(function () {
resolve(5)
}, 5000)
})
alert(result)
}
上面代码会等待5秒之后,弹出5
async 和 await必须成对出现
由于await的强制等待,所以必须要求使用await的函数必须使用async标记, async表示该函数就是一个异步函数,不会阻塞其他执行逻辑的执行
async test () {
const result = await new Promise(function(resolve){
setTimeout(function(){
resolve(5)
},5000)
})
alert(result)
}
test1(){
this.test() // 异步函数
alert(1)
}
通过上面的代码我们会发现,异步代码总是最后执行,标记了async的函数并不会阻塞整个的执行往下走
如果你想让1在5弹出之后再弹出,我们可以这样改造
async test1(){
await this.test()
alert(1)
}
// 这充分说明 被async标记的函数返回的实际上也是promise对象
如果promise异常了怎么处理?
promise可以通过catch捕获,async/ await捕获异常要通过 try/catch
async getCatch () {
try {
await new Promise(function (resolve, reject) {
reject(new Error('fail'))
})
alert(123)
} catch (error) {
alert(error)
}
}
async / await 用同步的方式 去写异步