学前了解:
异步编程:开发中无论是在浏览器环境中还是在Node环境中,我们都会使用JavaScript我完成各种异步操作(定时器、事件、ajax、读取文件、事件等),伴随着异步编程就避免不了回调机制的产生;会产生很多问题;
异步编程问题:产生回调地狱,难于维护和扩展;try catch只能捕获同步代码中的异常;同步并发的异步会存在一定的问题;
解决方案:ES6 Promise 可以解决回调地狱、以及同步并发的异步问题(异步操作的异常捕获可通过其他方法解决);但是依然会有明显的回调痕迹(generator 和async await 可以让异步代码看起来和同步一样,写起来更优雅);
学习内容:
一、基础知识:.then()、.catch()、.finally()、Promise.all()、Promise.race方法的学习;
二、用法:Promise上述几个方法的使用demo;
三、JS原生实现源码解析;
开始学习Promise!!!:
一基础知识:
1、.then():
先了解一下Promise的执行顺序:
宏任务:通过setTimeout()等方法创建的任务队列;
微任务:一些特定方法创建的任务;oP.then()方法创建的就是微任务;
微任务有优先执行权;
setTimeout( () => {
console.log(4)
}, 0);
const oP = new Promise( (resolve, reject) => {
//必须有一个内置函数,并且内置函数会在创建Promise时同步被执行;
console.log(1); //创建时同步执行;
resolve(3); //同步操作;输出顺序:1 2 3 4;
});
console.log(2);
oP.then((val) => {
//Promise中的.then(doneCallbacks, failCallbacks),
//只能注册两个回调函数:成功,失败;并且异步执行;
console.log(val);
}, (reson) => {
console.log(reson);
});
//最后的输出顺序:1 2 3 4;
链式操作(深入了解.then()的返回值):
const oP1 = new Promise( (resolve, reject) => {
setTimeout( () => { //承诺进行异步操作;
Math.random() * 100 > 60 ? resolve('及格') : reject('不及格');
//数字大于60时执行resolve(),小于60时执行reject()
}, 500);
});
oP1.then((val) => {
console.log('成功方法:' + val); //数字大于60被执行;
// throw new Error("失败");
//只要手动抛出一个错误就会执行下一个.then()的失败回调函数
//(不论在当前成功还是失败函数中return);
// return 10;
//如果被执行,return的参数将作为下一个.then()的参数传入;
return new Promise( (resolve, reject) => { //当return的是Promise()时
resolve('成功获取成功数据');
//执行哪个函数,可以决定下一个.then()中对应的函数执行;
});
}, (reson) => {
console.log('失败方法:' + reson); //数字小于60被执行;
// return 11;
//如果被执行,return的参数将作为下一个.then()的参数传入;
return new Promise( (resolve, reject) => {
reject('成功获取失败数据');
});
}).then( (val) => {
console.log('成功方法1:' + val);
//被执行;只要上一个.then(),不手动抛出错误或者不return一个
//new Pronise( (resolve, reject) => {reject('成功获取失败数据');)时,
//这一个.then()默认执行成功的函数;
}, (reson) => {
console.log('失败方法1:' + reson)
});
运行node,可将成功与失败的回调函数相对应执行,结果如下:
2、.catch()、 .finally():
以解决解决异常捕获问题为例(区分.then()与.catch()方法的异同):
const oP2 = new Promise( (resolve, reject) => {
resolve();
});
oP2.then(() => {
throw new Error('失败'); //手动在成功的回调中抛出一个错误;下一个.then()能不能捕获到;
}, (reson) => {
console.log(reson);
}).then(null, (reson) => {
console.log(reson); //可以在失败的回调中捕获到;捕获后仍可以执行后面你操作(10会作为参数传入);
return 10;
}).catch((err) => { //也可用.catch()异常捕获捕获失败;但是捕获后无法执行后面操作(10不会作参数传入下一个);
console.log(err);
return 10;
}).then( (val) => {
console.log('Error after ok:' + val); //捕获到失败后仍然可以执行.then()中的成功回调;
}).finally( () => {
console.log('over'); //结束这一系列链式调用;
});
.then()中失败的方法可以进行下一步传参:
.catch()中的return值不会当做参数传入下一个.then()中:
可见这两种方法都可以解决异常捕获问题,并且不会结束当前的链式调用;.finally()方法可结束链式调用;
3、Pronise.all():关注结果是成功还是失败状态;
①Pronise.all()可以将多个Promise实例包装成一个新的Pronise实例;(Promise对象以数组形式被包裹)
②成功和失败的返回值不同:成功返回一个结果的数组;失败返回最先被reject(num)的状态值;
③可用Pronise.all()全部成功状态的特性,来解决同步并发异步操作问题:
function text (num) {
return new Promise( (resolve, reject) => { //return new Promise();
setTimeout( () => { //进行一个异步操作;
// resolve(num); //假设全部成功:执行结果为:[ 'a', 'b', 'c' ]
Math.random() *100 > 50 ? resolve(num) : reject(num); //随机数大于50执行成功,否则执行失败;
}, 100)
});
}
const oP3 = Promise.all([text('a'), text('b'), text('c')])
.then( val => {
console.log('成功:' + val); //全成功时才会触发成功回调;结果为:[ 'a', 'b', 'c' ]
},(reason) => {
console.log('失败:' + reason); //有一个失败就返回最先被reject(num)的状态值;结果可能为 a || b || c;
});
4、Pronise.race():不关注结果是成功还是失败状态;
Promise.race()里面的哪个结果获得的快,就返回哪个结果(不关注结果是成功还是失败状态)
function text (num) {
return new Promise( (resolve, reject) => { //return new Promise();
setTimeout( () => { //进行一个异步操作;
// resolve(num); //假设全部成功:执行结果为:[ 'a', 'b', 'c' ]
Math.random() *100 > 50 ? resolve(num) : reject(num); //随机数大于50执行成功,否则执行失败;
}, 100)
});
}
const oP4 = Promise.race([text('a')])
.then( val => { //执行成功还是失败取决于text()中执行哪个回调;
console.log('成功:' + val);
},(reason) => {
console.log('失败:' + reason);
});
总结:由于Promise all() 全部成功才能执行成功的方法;Promise race() 有一个失败就不会执行成功方法;
所以通过上述两个方法无法实行一个需求(如果万一有失败,不去管,仍能对成功的进行处理)
//而 async + await 方法正好可以弥补Pronise的缺陷:
async function read1 () {
let val1 = null;
try{
val1 = await readFile('index1.txt');
console.log(val1);
}catch(e){
console.log(e);
}
}
async function read2 () {
let val2 = null;
try{
val2 = await readFile('index2.txt');
console.log(val2);
}catch(e){
console.log(e);
}
}
async function read3 () {
let val3 = null;
try{
val3 = await readFile('错误.txt');
console.log(val3);
}catch(e){
console.log(e);
}
}
function readAll(...args){
args.forEach(ele => {
ele();
});
}
readAll(read1, read2, read3);
//当有一个步骤出错后,相应的成功的仍然可调用成功的回调函数;
二、用法:Promise上述几个方法工作中的使用demo;
.then()解决地狱回调问题:
1、先来看一个一个地狱回调函数:
nodeo环境中运行:
const fs = require('fs');
fs.readFile('index1.txt', 'utf-8', (err, data) => {
//node中异步操作:读取3个文件;形成回调地狱;
//写出的代码不够优美,也不利于维护和扩展;
if (data) {
console.log(data);
fs.readFile(data, 'utf-8', (err, data) =>{
console.log(data);
if (data) {
fs.readFile(data, 'utf-8', (err, data) =>{
console.log(data);
});
}
});
}
});
2、Promise.then()解决之前的地狱回调函数:
const fs = require('fs');
function readFile (path) {
return new Promise( (resolve, reject) => { //return new Promise();
fs.readFile(path, 'utf-8', (err, data) => {
if (data) {
resolve(data);
//执行哪个函数,可以决定下一个.then()中对应的函数执行;
}
});
});
}
readFile('index1.txt').then( (data) => { //当执行成功回调函数时,可省略第二个参数;
return readFile(data);
}).then( (data) => {
return readFile(data);
}).then( (data) => {
console.log(data);
});
//同样可以获取的导数据;
可以看到使用Promise,依旧无法完全避开回调,从底层来讲也不可能完全避开,之后我会通过 generator 或 async + await方法 让该段异步代码看起来和同步一样,写起来更优雅;
拓:
async + await 方法实现:(注意:该方法是ES7方法)
async function read (url) {
//语义化更加好,写起来更像同步,代码更加优雅;
try{ //并且可可是用try catch监听异步;
let val1 = await readFile(url);
let val2 = await readFile(val1);
let val3 = await readFile(val2);
return val3;
}catch(e){
console.log(e)
}
}
read('index1.txt').then( val => { //直接一次调用即可;
console.log(val);
})
//同样可以获取的导数据;
三、JS源码:
点击Promise 原生JS全部实现进入下一篇…