1 Promise介绍和使用
1.1Promise是什么?
- 抽象表达
Promise是一门新的技术(ES6规范)
Promise是JS中进行异步编程的新解决方案(旧方案是单纯使用回调函数) - 具体表达:
从语法上来说:Promise是一个构造函数
从功能上来说:promise对象用来封装一个异步操作,并可以获取其成功/失败的结果值。
异步编程:
1. fs 文件操作
require('fs').readFile('./index.html', (err,data)=>{})
2. 数据库操作
3. AJAX
$.get('/server', (data)=>{})
4. 定时器
setTimeout(()=>{}, 2000)
1.2 Promise优势
- 指定回调函数的方式更加灵活
- 支持链式调用,可以解决回调地狱问题
1.3 Promise基本使用
const p = new Promise(resolve, reject){
setTimeout(() => {
let n = rand(1, 100);
if(n <= 30)
resolve(n)
else
reject(n)
}, 2000)
}
p.then((value)=>{
alert("success")
}, (reason)=>{
alert("default")
})
1.4 util.promisify(original)
- original: function
- 返回:function
- 传入一个遵循常见的错误优先的回调风格的函数( (err, value)=>{} )
- 将普通回调函数转换成 promise
1.5 Promise状态
- pending 未决定的 --> resolved/rejected
- resolved / fullfilled 成功
- rejected 失败
- 说明:只能改变一次
无论变为成功还是失败,都会有一个结果数据。
成功的结果数据一般称为value,失败的结果数据一般称为reason。
1.6 Promise对象的值
- 保存着异步任务 成功/失败 的结果;
- 可以通过 resolve/reject 进行修改
1.7 Promise 基本流程
1.8 Promise封装AJAX请求
const p = new Promise((resolve, reject) => {
const xhr = XMLHTTPRequest();
xhr.open('GET', 'url');
xhr.send();
xhr.onreadystatechange = function(){
if(xhr.readyState >= 200 && xhr.readyState < 300) {
resolve(xhr.response)
}else{
reject(xhr.status)
}
}
});
p.then(value=>{
console.log(value);
}, reason => {
console.warn(reason)
})
2 Promise API
2.1 Promise构造函数:Promise(excutor){}
- executor 函数:执行器 (resolve, reject) =>{}
- resolve 函数:内部定义成功时我们调用的函数 value=>{}
- reject 函数:内部定义失败时我们调用的函数 reason=>{}
- 说明:executor会在Promise内部立即同步调用,异步操作在执行器中执行。
2.2 Promise.prototype.then 方法:(onResolved, onRejected)=>{}
- onResolved 函数:成功的回调函数 (value)=>{}
- onRejected函数:失败的回调函数 (reason)=>{}
- 说明:指定用于得到成功value的成功回调和用于得到失败reason的失败回调,返回一个promise对象。
2.3 Promise.prototype.catch方法:(onRejected)=>{}
- onRejected函数:失败的回调函数 (reason)=>{}
2.4 Promise.resolve方法:(value)=>{}
- value:成功的数据或promise对象
- 返回一个成功/失败的 promise 对象
2.5 Promise.reject方法:(reason)=>{}
- reason:失败的原因
- 返回一个失败的promise对象
2.6 Promise.all方法:(promises)=>{}
- promises:包含n个promise的数组
- 返回一个新的promise,只有所有的romise都成功才成功,一个失败就直接失败
2.7 Promise.race方法:(promises)=>{}
- promises:包含n个promise的数组
- 返回一个新的promise,第一个完成的promise的结果状态就是最终的结果状态
3 Promise 关键问题
3.1 如何改变Promise的状态?
- resolve(value):如果当前是pending就会变为resolved
- reject(reason):如果当前是pending就会变为rejected
- 抛出异常:如果当前是pending就会变成rejected
3.2 一个promise指定多个成功/失败回调函数,都会调用吗?
- 当promise改变为对应状态时都会调用
let p = new Promise((resolve, reject) => {
resolve("OK");
})
// 指定回调1
p.then(value => {
console.log(value); // OK
})
// 指定回调2
p.then(value => {
console.log(value); // OK
})
3.3 改变promise状态和指定回调函数谁先谁后?
- 都有可能,正常情况下是先指定回调再改变状态,但也可以先改状态再指定回调
- 什么情况下先指定回调再改变状态?
- 执行器内部是一个异步任务
- 如何先改状态再指定回调?
- 在执行器中直接调用 resolve() / reject()
- 延长更长的时间才调用 then()
- 什么时候才能得到数据?
- 如果先指定的回调,那当状态发生改变时,回调函数就会调用,得到数据;
- 如果先改变状态,那当指定回调时,回调函数就会调用,得到数据。
3.4 promise.then()返回的新的promise的结果状态由什么决定?
- 简单表述:由then()指定的回调函数执行的结果表示
- 详细描述:
- 如果抛出异常,新的promise变为rejected,reason为抛出的异常;
- 如果返回的是非promise的任意值,新的promise变为resolved,value为返回的值;
- 如果返回的是另一个新的promise,此promise的结果就会变成新promise的结果。
3.5 promise如何串联多个操作任务?
- promise的then()返回一个新的promise,可以开启then()的链式调用。
- 通过then的链式调用串联多个同步/异步任务。
let p = new Promise((resolve, reject) => {
resolve("OK");
})
p.then(value => {
return new Promise( (resolve, reject) => {
resolve("success");
})
}).then(value => {
console.log(value); // success
}).then(value => {
console.log(value); // undefined
})
3.6 promise异常穿透
- 当使用 promise 的 then 链式调用时,可以在最后指定失败的回调;
- 前面任何操作出了异常,都会传到最后失败的回调中处理。
let p = new Promise((resolve, reject) => {
reject("error");
})
p.then(value => {
console.log(111);
}).then(value => {
console.log(222);
}).then(value => {
console.log(333);
}).catch(reason => {
console.warn(reason); // error
})
3.7 中断promise链
- 当使用promise的then链式调用时,在中间中断,不再调用后面的回调函数
- 办法:在回调函数中返回一个pendding状态的promise对象。
let p = new Promise((resolve, reject) => {
resolve("OK");
})
p.then(value => {
console.log(111);
}).then(value => {
console.log(222);
// 中断:有且只有一种方式
return new Promise(() => {});
}).then(value => {
console.log(333);
}).catch(reason => {
console.warn(reason);
})
4 Promise自定义封装
git地址:https://github.com/danfeng0205/My-Promise.git
4.1 构造函数
- 对于同步任务,先修改状态再执行回调;
- 对于异步任务,由于先指定了回调,然后才开始修改状态,再执行回调。
因此需要先保存我们的回调(callbacks,因为一个promise可以指定多个成功/失败回调函数且都会执行,因此采用数组进行保存),然后再修改状态(resolve/reject)时再执行回调。
function Promise(executor) {
// 添加属性
this.PromiseState = 'pending';
this.PromiseResult = null;
// 用于保存异步请求中promise对象
this.callbacks = [];
// 由于resolve/reject是直接调用(this指向window)
// 将this保存,指向该实例对象
const self = this;
// resolve函数
function resolve(data) {
// 判断状态,保证只执行一次
if (self.PromiseState != 'pending')
return
// 修改对象的状态
self.PromiseState = "fullfilled";
// 设置对象的结果值
self.PromiseResult = data;
// setTimeout是为了实现then方法中的事件是异步操作
// 对于异步请求真正执行的时刻
setTimeout(()=>{
self.callbacks.forEach(item => {
item.onResolved(data);
})
})
}
function reject(data) {
if (self.PromiseState != 'pending')
return
self.PromiseState = "rejected";
self.PromiseResult = data;
setTimeout(() => {
self.callbacks.forEach(item => {
item.onRejected(data);
})
});
}
// 对抛出异常的处理
try {
executor(resolve, reject);
} catch (error) {
reject(error)
}
}
4.2 then
- 对于同步任务,先修改状态再执行回调;
- 对于异步任务,由于先指定了回调(pending),然后才开始修改状态,再执行回调。因此需要判断三种状态(pending、fullfilled、rejected)的情况
- fullfilled:执行onResolved
- rejected:执行onRejected
- pending:此时还未修改状态,将回调保存在callbacks中
Promise.prototype.then = function (onResolved, onRejected) {
const self = this;
// 穿透
if (typeof onRejected !== 'function') {
onRejected = reason => {
throw reason;
}
}
if (typeof onResolved !== 'function') {
onResolved = value => {
return value;
}
}
return new Promise((resolve, reject) => {
function callback(type) {
try {
let result = type(self.PromiseResult);
if (result instanceof Promise) {
result.then(v => {
resolve(v)
}, r => {
reject(r)
})
} else {
// 结果对象状态为成功
resolve(result);
}
} catch (e) {
reject(e)
}
};
if (this.PromiseState === 'fullfilled') {
// 获取回调函数的执行结果
// setTimeout是为了实现then方法中的事件是异步操作
setTimeout(()=>{
callback(onResolved)
})
}
if (this.PromiseState === 'rejected') {
setTimeout(()=>{
callback(onRejected)
})
}
if (this.PromiseState === 'pending') {
// 当状态改变之后,就会执行
this.callbacks.push({
onResolved: function () {
// 执行成功的回调函数
callback(onResolved)
},
onRejected: function () {
callback(onRejected)
}
});
}
})
}
4.3 catch
Promise.prototype.catch = function (onRejected) {
return this.then(undefined, onRejected);
}
4.4 resolve
- 静态resolve方法:如果返回一个成功/失败的 promise 对象(取决于promise对象执行结果);如果返回一个非promise对象,则状态为成功。
Promise.resolve = function (value) {
return new Promise((resolve, reject) => {
if (value instanceof Promise) {
value.then(v => {
resolve(v);
}, r => {
reject(r)
})
} else {
resolve(value)
}
})
}
4.5 reject
- 静态 reject 方法返回一个失败的promise对象,因此直接调用 reject()。
Promise.reject = function (reason) {
return new Promise((resolve, reject) => {
reject(reason)
})
}
4.6 all
- 需要声明一个数组存放成功的结果,且为了保证存放的顺序始终一致,需要用arr[i],而不能采用push方法。
- 当成功的数量(count)和promises数组长度一样时,代表全部成功。
Promise.all = function (promises) {
// 返回结果为Promise对象
return new Promise((resolve, reject) => {
// 声明变量(计数器)
let count = 0;
// 存放成功结果
let arr = [];
// 遍历
for (let i = 0; i < promises.length; i++) {
promises[i].then(v => {
count++;
arr[i] = v;
if (count === promises.length) {
resolve(arr);
}
}, r => {
reject(r);
})
}
})
}
4.7 race
Promise.race = function (promises) {
// 返回结果为Promise对象
return new Promise((resolve, reject) => {
// 遍历
for (let i = 0; i < promises.length; i++) {
promises[i].then(v => {
// 修改返回对象的状态为成功
resolve(v);
}, r => {
// 修改返回对象的状态为失败
reject(r);
})
}
})
}
5 async与await
5.1 async函数
- 函数的返回值为promise对象
- promise对象的结果由async函数执行的返回值决定
-
如果返回一个非promise类型数据,状态为成功,PromiseResult 是返回值;
-
如果返回一个Promise对象,由promise对象决定;
-
抛出异常,返回一个失败的promise对象,PromiseResult 是返回的值 ;
-
async function main(){
// 1.返回值如果是一个非promise类型的数据
// return 521
// 2.返回一个Promise 对象
// return new Promise((resolve, reject) => {
// // throw 'x'
// reject('Error')
// resolve('OK')
// })
// 3.抛出异常
throw 'error'
}
let res = main();
console.log(res)
5.2 await表达式
- await右侧的表达式一般为promise对象,但也可以是其他的值;
- 如果表达式是promise对象,await返回的是promise成功的值
let p = new Promise((resolve, reject) => {
// throw 'x'
reject('Error')
// resolve('OK')
})
// 1.promise成功
let res = await p; // OK
// 2.非promise
let res2 = await 20; // 20
// 3.promise失败,需要try catch
//let res3 = await p; // Uncaught (in promise) Error
try {
let res3 = await p
} catch (error) {
console.log(error);
}
5.3 注意
- await 必须写在 async 函数中,但 async 函数中可以没有 await;
- 如果 await 的 promise 失败了,就会抛出异常,需要通过 try catch 捕获处理
/*
读取三个文件内容
*/
// 回调函数的方式
const { log } = require('console');
const fs = require('fs');
const util = require('util');
const mineReadFile = util.promisify(fs.readFile)
// 回调地狱
fs.readFile('./resource/1.txt', (err, data1) => {
if (err) throw err;
fs.readFile('./resource/2.txt', (err, data2) => {
if (err) throw err;
fs.readFile('./resource/3.txt', (err, data3) => {
if (err) throw err;
console.log(data1 + data2 + data3);
})
})
})
// 改写成async await
async function main() {
try {
let data1 = await mineReadFile('./resource/1.txt');
let data2 = await mineReadFile('./resource/2.txt');
let data3 = await mineReadFile('./resource/3.txt');
console.log(data1 + data2 + data3);
} catch (error) {
console.log(error);
}
}
main();