ES6——异步编程Promise全方位学习,从零到源码(.then()、.catch()、.finally()、Promise.all())

ES6——异步编程Promise学习(从零到源码)

学前了解:

异步编程:开发中无论是在浏览器环境中还是在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,可将成功与失败的回调函数相对应执行,结果如下:
ES6——异步编程Promise全方位学习,从零到源码(.then()、.catch()、.finally()、Promise.all())_第1张图片

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);
                });
            }
        });
    }
});

运行node,结果如下:
在这里插入图片描述

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全部实现进入下一篇…

你可能感兴趣的:(ES6)