Promise的介绍
1. Promise的介绍
Promise是es6引入的异步编程的解决方法,从语法上说,Promise就是一个构造函数。从功能上说,Promise就是封装了一个异步操作,并可以获取其成功或失败的结果。
2. 常见的异步操作
- fs文件读取:require("fs").readFile('index.html',(err,data)=>{})
- 数据库操作
- ajax请求:$.get(url,()=>{})
- setTimeout(()=>{},time)
- setInterval(()=>{},time)
3. Promise的优势:
- 指定回调函数的方式更加灵活 - 旧:必须在启动异步任务前指定 - promise:启动异步任务->返回promise对象-给promise对象绑定指定回调函数(甚至可以在异步任务结束后指定多个)
- 支持链式调用,可以解决回调地狱问题 - 回调地狱:回调函数嵌套使用,外部回调函数异步执行的结果是嵌套的回调执行的条件 - 不便于阅读、不便于异常处理。 - 可以通过promise链式调用解决。 -
4. Promise对象
const promise = new Promise((resolve,reject)=>{
console.log(111)
});
console.log(promise);
/*
Promise {<pending>}
[[Prototype]]: Promise
[[PromiseState]]: "pending"
[[PromiseResult]]: undefined
*/
- promise的状态
- 实例对象中的一个属性,【PromiseState】
- pending---未决定的
- resolved/fullfilled---成功
- rejected---失败
- 状态只能从pending变为resolved/rejected,不能从resolved->rejected,rejected->resolved
- 状态只能改变一次
- promise 对象的值
- 实例对象中的另一个属性,【PromiseResult】,保存着对象[成功/失败]的结果。
- resolve
- reject
5. promise的工作流程
- 通过new Promise()创建一个promise对象,在promise内部封装异步操作。
- 如果异步操作成功,则调用resolve方法,并将promise的状态设置为resolved,并调用then()的第一个回调。返回一个新的promise对象。
- 如果异步操作失败,则调用reject方法,并将promise的状态设置为rejected,并调用then()的第二个回调。返回一个新的promise对象。
6. promise的API
let p = new Promise((resolve,reject)=>{
console.log(111);
})
console.log(222);
---先输出111后输出222
- Promise的构造函数:Promise(executor), ----executor函数:(resolve,reject)=>{@} ----resolve为成功的回调,reject为失败的回调 ----executor会在Promise内部立即同步执行,异步操作在执行器中执行。
- Promise.prototype.then((onResolved,onReject)=>{}),分别是成功和失败的回调
- Promise.prototype.catch(reson=>{}):失败的回调
- Promise.resolve方法:(value)=>{},返回成功/失败的promise对象
- Promise.reject方法:返回一个失败的promise对象
- Promise.all:(promises)=>{},参数是一个数组,只有所有promise都成功才成功。只要有一个失败了就直接失败。
- Promise.race:(promises)=>{},参数是一个数组,第一个promise的状态绝对最后结果。
7. promise的几个关键问题
- 指定回调的方法?-- then() 或 catch()
- 修改对象的状态?调用resolve() 或 reject() 或 throw
let promise = new Promise((resolve,reject)=>{resolve('ok')})
let promise = new Promise((resolve,reject)=>{reject('fail')})
let promise = new Promise((resolve,reject)=>{throw 'error'})
- 能否执行多个回调?当promise改为对应状态时,都会调用
let promise = new Promise((resolve,reject)=>{
reject('ok')
})
promise.then((resolve)=>{
console.log("执行了-1")
}).then((resolve)=>{
console.log("执行了-2")
});
// 先输出"执行了-1",然后输出"执行了-2"
- 指定回调与改变状态的执行顺序
-- 改变状态和指定回调的执行顺序,谁先谁后? 答:都有可能,正常情况是先指定回调函数在改变状态,但是也可以先改变状态再指定回调
-- 如何先改变状态再指定回调?
- 在执行器中直接调用resolve()/reject()
- 延长更长时间才调用then()
-- 什么时候才得到数据?
- 如果先指定回调,当状态改变时,回调函数就会被调用,得到数据
- 如果先改变状态,那当指定回调时,回调函数就会被调用,得到数据
Promise初体验
1. 初体验--抽奖按钮
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>promise体验</title>
</head>
<body>
<button id="btn">抽奖按钮</button>
<script>
function rand(m, n) {
return Math.ceil(Math.random() * (n - m + 1) + m - 1);
}
const btn = document.getElementById("btn");
// 1. 通过setTimeout实现
/*btn.addEventListener("click", function () {
setTimeout(() => {
if (n < 30) {
console.log("恭喜你中奖了", n);
} else {
console.log("再接再厉!", n);
}
}, 2000);
})*/
// 2. 通过Promise实现
btn.addEventListener("click", function () {
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
let n = rand(1, 100);
if (n < 30) {
resolve(n);// 将promise对象的状态设置为【成功】
} else {
reject(n);//将promise对象的状态设置为【失败】
}
}, 2000);
});
promise.then(
(n) => {//resolve
console.log("恭喜你中奖了", n);
},
(n) => {//reject
console.log("再接再厉!", n);
});
})
</script>
</body>
</html>
2. 初体验--读取文件内容
// Promise初体验 - 读取文件内容
// node filename.js
const fs = require("fs");
// 1. 回调函数形式
// fs.readFile("./resource/readme.txt", (err, data) => {
// if (err) throw err;
// console.log(data.toString());
// });
// 2. Promise
let promise = new Promise((resolve, reject) => {
fs.readFile("./resource/readme.txt", (err, data) => {
if (err) {
reject(err);
} else {
resolve(data);
}
});
})
promise.then(
(data) => {//resolve
console.log(data.toString());
},
(err) => {//reject
throw err;
}
);
3. 初体验--封装http请求
/*
封装一个函数sendAjax 发送http请求
参数:url,type
返回:promise对象
*/
function sendAjax(url, type) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open(type, url);
xhr.send();
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300) {
resolve(xhr.response);
} else {
reject(xhr.status);
}
}
}
});
}
sendAjax('http://localhost:2000/server', 'get').then((
data) => { console.log(data) },
(err) => { console.log(err) }
);
4. 初体验--封装读取文件的方法
/*
封装一个函数mineReadFile 读取文件内容
参数:path-文件路径
返回:promise对象
*/
function mineReadFile(path) {
return new Promise((resolve, reject) => {
// 读取文件
require("fs").readFile(path, (err, data) => {
// 判断是否读取成功
if (err) reject(err);
resolve(data);
});
// .then(
// (data) => {
// console.log(data)
// },
// (err) => {
// console.log(err);
// }
// );
})
}
mineReadFile("./resource/readme.txt").then(
(data) => { console.log(data.toString()) },
(err) => { console.log(err) });
Promise API
1. resolve方法
// 1. 传入的参数为非promise对象,则返回一个状态为成功的promise对象
// 2. 传入的参数为promise对象,则,参数的结果决定了resolve的结果
let promise1 = Promise.resolve(new Promise((resolve, reject) => {
// resolve('success');
reject('fail')
}));//传入:123
console.log(promise1);
2. reject方法
// 1. 传入的参数为非promise对象,则返回一个状态为失败的promise对象
// 2. 传入的参数为promise对象,则,参数的结果决定了reject的结果
let promise2 = Promise.reject(new Promise((resolve, reject) => {
resolve('success');
}));//11
console.log(promise2);
3. all方法
// 参数:一个数组
// 返回值:所有Promise都成功,状态才是成功,只要有一个是失败,就为失败。
let arr = [
new Promise((resolve, reject) => {
setTimeout(() => {
resolve('success');
}, 1)
}),
new Promise((resolve, reject) => {
// resolve('success');
reject('fail');
}),
new Promise((resolve, reject) => {
resolve('success');
})];
let promise4 = Promise.all(arr);
console.log(promise4);
4. race方法
// 参数为数组,返回值为第一个promise的状态即为最终状态
let arr = [
new Promise((resolve, reject) => {
setTimeout(() => {
resolve('success');
}, 1)
}),
new Promise((resolve, reject) => {
// resolve('success');
reject('fail');
}),
new Promise((resolve, reject) => {
resolve('success');
})];
let promise3 = Promise.race(arr);
console.log(promise3);
// apply bind call
Promise中的一些重点
1. 通过then方法指定回调
指定回调和改变状态的执行顺序是不一定的喔,要视情况而定。指定回调是通过then方法传参(resolve,reject)来指定的,而状态的改变是在调用回调(resolve/reject)的那一刻。并且状态只能改变一次。
/*
Q:改变状态和指定回调的执行顺序,谁先谁后?
A:都有可能,正常情况是先指定回调函数再改变状态,但是也可以先改变状态再指定回调。
Q:如何先改变状态再指定回调?
A:1. 在执行器中直接调用resolve()/reject()
2. 延长更长时间才调用then()
Q:什么时候才得到数据?
A:1. 如果先指定回调,当状态改变时,回调函数就会被调用,得到数据
2. 如果先改变状态,那当指定回调时,回调函数就会被调用,得到数据
*/
// 1. 先改变状态,后指定回调
let promise1 = new Promise((resolve, reject) => {
resolve("1-resolve-ok");
console.log("1-resolve-ok");
});
promise1.then((resolve) => {
console.log('2-then-resolve');
});
// 2. 先指定回调,后改变状态(注意是指定回调而不是执行回调)
let promise2 = new Promise((resolve, reject) => {
setTimeout(()=>{
resolve("1-resolve-ok");
console.log("1-resolve-ok");
},1000);
});
promise2.then((resolve) => {
console.log('2-then-resolve');
});
2. then方法的返回值
/*
then的返回结果,由then指定的回调函数执行的结果决定的
详细来说:
1. 如果抛出异常,新promise变为reject,reason为抛出的异常
2. 如果返回的是非promise的任意值,,新promise变为resolve,value为返回的值
3. 如果返回的是另一个新的promise,此promise的结果,就是新promise的结果
4. 默认状态'fullfilled',默认值undefined
*/
let promise = new Promise((resolve, reject) => {
resolve('ok')
});
let result = promise.then(value => {
console.log(value);
/*
1. 抛出异常
throw 'error';//promise,reject,'error';
result为:
Promise
[[Prototype]]: Promise
[[PromiseState]]: "rejected"
[[PromiseResult]]: "error"
*/
//
/*
2. 返回非promise的值
return 123;
result为:
Promise {<pending>}
[[Prototype]]: Promise
[[PromiseState]]: "fulfilled"
[[PromiseResult]]: 123
*/
return new Promise((resolve,reject)=>{reject('error')});
/*
1. 返回Promise对象
result为:
Promise {<pending>}
[[Prototype]]: Promise
[[PromiseState]]: "rejected"
[[PromiseResult]]: "error"
*/
}, reason => {
console.log(reason);
})
console.log(result);// promise对象
3. Promise串联多个操作任务
/*
promise的then方法返回一个promise,可以看成then的链式调用
通过then的链式调用串联多个同步/异步任务
*/
let promise = new Promise((resolve, reject) => {
//resolve('ok');//同步
setTimeout(() => {//异步
resolve('1-ok');
}, 1000);
});
promise.then(value => {
return new Promise((resolve, reject) => {
resolve('2-ok');
})
}).then(value => {
console.log('3-ok', value);//2-ok
}).then(value => {
console.log('4-ok', value);//undefined
});
console.log(promise);
/*
Promise {<pending>}
[[Prototype]]: Promise
[[PromiseState]]: "fulfilled"
[[PromiseResult]]: "1-ok"
*/
4. 异常穿透
/*
promise异常穿透
1. 当使用promise的then链式调用时,可以在最后指定失败的回调
2. 前面任何操作出了异常,都会传到最后失败的回调中处理
中断promise链
1. 当使用promise的then链式调用时,在中间中断,不再调用后面的回调函数
2. 解决办法:在回调函数中,返回一个pedding状态的promise对象
*/
let promise = new Promise((resolve, reject) => {
// setTimeout(() => {
// reject('error');
// }, 1000);
throw 'error';
});
let result = promise.then(value => {
console.log(111);
}, reason => {
console.log(reason);
return 456;
})//.then(value => {
// console.log(222);
// }).then(value => {
// console.log(333);
// })
.catch(reason => {
console.log(reason);
return 123;
}); // 按顺序执行,并最终进入到catch,不用每个then方法都写error的回调
console.log(result);
let promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('error')
}, 1000);
});
promise1.then(value => {
console.log(111);
return new Promise(() => { })// 返回pedding状态的promise对象,之后的then方法就不会执行了
}, reason => {
console.log(reason);
throw reason;
return new Promise(() => { })
}).then(value => {
console.log(222);
}).then(value => {
console.log(333);
}).catch(reason => {
console.log(reason);
});
5. async函数
/*
1. async函数的返回值为一个promise对象
2. 对象的值由函数的返回值由async函数的返回值决定
3. async函数相当于promise的then方法
*/
async function main() {
// 1. 返回非promise的值
// return 123;
/*
res:
Promise
[[Prototype]]: Promise
[[PromiseState]]: "fulfilled"
[[PromiseResult]]: 123
*/
// 2. 返回promise值
return new Promise((resolve, reject) => {
// resolve('ok');
/*
Promise {<pending>}
[[Prototype]]: Promise
[[PromiseState]]: "fulfilled"
[[PromiseResult]]: "ok"
*/
reject('error');
/*
Promise {<pending>}
[[Prototype]]: Promise
[[PromiseState]]: "rejected"
[[PromiseResult]]: "error"
*/
// 3. 抛出异常
// throw 'oh no';
/*
Promise {<pending>}
[[Prototype]]: Promise
[[PromiseState]]: "rejected"
[[PromiseResult]]: 'oh no'
*/
})
}
let res = main();
console.log(res)
/*
main函数没有返回值:
res:
Promise
[[Prototype]]: Promise
[[PromiseState]]: "fulfilled"
[[PromiseResult]]: undefined
*/
6. await表达式
/*
1. await右侧的表达式一般为promise对象,但也可以为其他值
2. 如果是promise对象,则await返回的是promise成功的值
3. 如果表达式的是其他值,直接将此值作为await的返回值
注意:
1. await必须写在async函数中,但是async函数中可以没有await
2. 如果await的promise失败了,就会抛出异常,需要通过try...catch捕获处理
*/
async function main() {
let p = new Promise((resolve, reject) => {
// resolve('ok');
reject('error');
});
// 1. 非promise
let res1 = await 123;
console.log(res1);//123
// 2. 为promise -成功
// let res = await p;
// console.log(res);//ok
// 3. 为promise -失败
try {
let res2 = await p;
} catch (e) {
console.log(e);//error
}
}
main();
7. async和await的实践--读取文件内容
// node filename.js
const fs = require('fs');
const util = require('util');
const mineReadFile = util.promisify(fs.readFile)
// 读取文件
// fs.readFile('./resource/readme.txt', (err1, data1) => {
// if (err1) throw err1;
// fs.readFile('./resource/txt.txt', (err2, dada2) => {
// if (err2) throw err2;
// console.log(data1 + dada2);
// })
// });
async function main() {
try {
let data1 = await mineReadFile('./resource/readme.xt');
let data2 = await mineReadFile('./resource/txt.txt');
console.log(data1 + data2);
} catch (e) {
console.log(e.code);
}
}
main();
8. async和await的实践--ajax请求
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>async与await</title>
</head>
<body>
<button id="btn">点击发送</button>
<script>
function sendAjax(url, type) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open(type, url);
xhr.send();
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300) {
resolve(xhr.response);
} else {
reject(xhr.status);
}
}
}
});
}
// sendAjax('http://localhost:2000/server', 'get').then((
// data) => { console.log(data) },
// (err) => { console.log(err) }
// );
const btn = document.getElementById('btn');
btn.addEventListener('click', async function () {
// 发送
let res = await sendAjax('http://localhost:2000/server', 'get');
console.log(res);
})
</script>
</body>
</html>