【ES6】 Promise相关(事件循环,宏/微任务,promise,await/await)


1 同步与异步

1-1 同步

​ 可以理解为程序在执行时,每个代码任务需等到上一个代码任务执行结束后才能执行(类比成排队买票后面的人必须等到前面的人买完才能买到票)。

console.log('第一个执行任务');

console.log('第二个执行任务');

console.log('第三个执行任务');

/*
1
2
3 
*/

1-2 异步

​ 可以理解为:自己做饭需要买菜【同步】,买厨具,买燃气,自己出门买意味着自己需要等到单独一件事(如买菜)完成后才能进行下一项购买,而当使用手机购买时【异步】,你只需要向商家告诉一声,然商家配送,这个过程中自己无需等到前一个事情完成再去通知下一家商家,当商品送到时接收一下就行。

​ 因为js代码是单线程执行的,所以本身是没有异步的,但是JavaScript中有一个特殊的函数,即回调函数,通过回调函数(callback Function)js实现了异步。

console.log('1');

setTimeout(() => {
    console.log('2');
});

console.log('3');

/*
1
3
2
*/

2 执行栈和事件队列(task queue)以及事件循环

​ 1,在javascript代码执行的过程中会将不同的变量存于内存的不同位置:堆:(head)栈(stack)中加以区分。堆中存放的是一些对象,栈中存放的一些基础类型的变量以及对象的指针。

​ 2,在我调用一个方法的时候 ,JavaScript会生成一个执行环境(context), 又叫做执行上下文, 这个执行环境中存在着这个方法的私有作用域,上层作用域的指向,方法的参数,这个作用域中定义的变量以及这个作用域的this对象。 而当一系列方法被依次调用的时候,因为js是单线程的,同一时间只能执行一个方法,于是这些方法被排队在一个单独的地方。这个地方被称为__执行栈__。

​ 3,当代码第一次执行的时候,js引擎会解析这段代码,并将其中的同步代码按照执行顺序加入执行栈中,然后从头开始执行。如果当前执行的是一个方法,那么js会向执行栈中添加这个方法的执行环境,然后进入这个执行环境继续执行其中的代码。当这个执行环境中的代码 执行完毕并返回结果后,js会退出这个执行环境并把这个执行环境销毁,回到上一个方法的执行环境。。这个过程反复进行,直到执行栈中的代码全部执行完毕。

​ 4,js引擎遇到一个异步事件后并不会一直等待其返回结果,而是会将这个事件挂起,继续执行执行栈中的其他任务。当一个异步事件返回结果后,js会将这个事件加入与当前执行栈不同的另一个队列,我们称之为事件队列。被放入事件队列不会立刻执行其回调,而是等待当前执行栈中的所有任务都执行完毕, 主线程处于闲置状态时,主线程会去查找事件队列是否有任务。如果有,那么主线程会从中取出排在第一位的事件,并把这个事件对应的回调放入执行栈中,然后执行其中的同步代码…,如此反复,这样就形成了一个无限的循环。这就是这个过程被称为“事件循环(Event Loop)”的原因。

3 宏任务与微任务

我们所知道的异步代码有setTimeout和setInterval, ajax请求, promies对象then()async/await, dom事件,虽然它们都是异步事件,但是它们还是有很大的区别,

3-1 宏任务,微任务的区别

  • 宏任务包括定时器函数Dom事件ajax请求

  • 微任务包括Promise.resolve().then()async\await

  • 执行时机上,微任务比宏任务执行时机早,当主执行栈中代码执行完毕时,主执行栈会先去事件队列中查找微任务放入执行栈执行,后再查找宏任务放入执行栈执行

  • 微任务在Dom渲染之前就可以执行,而宏任务在Dom渲染之后可以执行

3-2 执行

  • 在一个事件循环中,异步事件返回结果后会被放到一个任务队列中。然而,根据这个异步事件的类型,这个事件实际上会被对应的宏任务队列或者微任务队列中去。并且在当前执行栈为空的时候,主线程会 查看微任务队列是否有事件存在。如果不存在,那么再去宏任务队列中取出一个事件并把对应的回到加入当前执行栈;如果存在,则会依次执行队列中事件对应的回调,直到微任务队列为空,然后去宏任务队列中取出最前面的一个事件,把对应的回调加入当前执行栈…如此反复,进入循环。
  • 在js代码刚开始执行时,script标签会进入宏任务队列,在此宏任务队列之前没有任何微队列,所以该script会进入调用栈中执行其中的同步代码,然后微任务压入微任务队列,宏任务压入宏任务队列,当调用栈中执行完毕后会先去执行微任务队列代码,执行完毕后在执行宏任务队列中的第一条宏任务,其中的宏任务微任务都会压入对应队列,当每个宏任务执行完全后,都会清除对应的微任务队列代码。不断往复

简而言之当前执行栈执行完毕时会立刻先处理所有微任务队列中的事件,然后再去宏任务队列中取出一个事件(第一个)。同一次事件循环中,微任务永远在宏任务之前执行。


4 Promise

4-0 Promise/A+规范

ES6新增对Pormise/A+规范的完善支持,即Promise类型,它成为了主导性的异步编程机制。

ES6新增的引用类型Promise,可以通过new操作符来实例化。创建新期约时需要传入执行器(exector)函数只作为参数

// 创建Promise
// 接收一个参数函数
// 参数函数接收两个参数
// resolve , reject
let p = new Promise((res , rej)=>{});

4-1 Promise是什么?

  • 1抽象表达
    • Promise是es6的一项新的技术。
    • Promise是js中进行异步编程的新解决方案(旧的是单纯使用回调函数)
  • 具体表达:
    • 从语法上说是一个构造函数
    • 从功能上说是用来封装一个异步操作并可以获取其成功/失败的结果值

4-2 为什么使用Promise?

4-2-1 指定回调函数更加的灵活

​ 1 旧的: 必须在启动异步任务前指定

​ 2 promise:启动异步任务 => 返回Promise对象 => 给Promise对象绑定回调函数(甚至在异步任务结束后指定多个)

4-2-2 支持链式调用,可以解决回调地狱的问题

  • 什么是回调地狱问题?

    到我们需要执行大量的异步任务,且得到的结果要有同步的规则的输出时(上一部回调的结果作为下一步异步函数回调的条件时),我们会将大量的回调嵌套起来,这时候代码整体可读性非常的差,也就出现了回调地狱的现象

  • 回调地狱的特点?

    ​ 不便于阅读

    ​ 不便于异常处理

  • 解决方案

    ​ promise 链式调用

fs.readFile('./a.txt', function (err, date) {
     if (err) {
         return console.log('读取错误');
     }
     console.log(date.toString());
     fs.readFile('./b.txt', function (err, date) {
         if (err) {
             return console.log('读取错误');
         }
         console.log(date.toString());
         fs.readFile('./引入.html', function (err, date) {
             if (err) {
                 return console.log('读取错误');
             }
             return console.log(date.toString());
         });
     });
 });
// 此时可以解决问题,但是如果业务过多会出现维护困难的情况----------回调地狱----代码呈现 '>' 型

4-3 Promise状态[PromsieState]的改变

4-3-1 期约状态

当我们创建期约实例时,这个实例可能处于三个状态

  • 待定 pendding
  • 兑现 fulfilled (也称为解决 , resolved)
  • 拒绝 rejected

待定是期约的最初始的状态。在待定状态下,期约可以转换为兑现状态,或者转换为拒绝状态。

待定状态可以转换为兑现状态或者拒绝状态,但是拒绝状态或者兑现状态不能转换为待定状态,且兑现状态和拒绝状态之间不能转换

期约的状态是私有的, 通过js是无法访问到实例的期约状态。主要是为了避免读取到期约状态,以同步的方式处理状态

在这里插入图片描述

当把一个期约实例打印的时候

let promise = new Promise((resolve, reject)=>{
    setTimeout(() => {
        resolve('定时器结束');
    })
});

console.log(promise);
// Promise {<pending>}

此时这个实例便处于待定状态( pendding )。


4-3-2 Promise对象中的值

实例对象中的另一个属性 [PromiseResult]

即对象[成功/失败]的结果

只有两个方法能够改变这个值

  • resolve
  • reject

4-4 Promise的基本工作流程

!!!

​ 1> new Promise创建一个的Promise实例对象 ,并在其内部创建一个异步任务

​ 2> 执行其中的异步操作 ,如果异步操作执行成功,则调用resolve();这个函数,更改Promise实例的[PromiseState]的状态为__resolved__(成功)

​ 3> 成功时调用then()方法更改Promise的值[PromiseResult]的值,并执行成功的回调,返回一个新的Promise对象__(链式调用的核心)__

​ 4 > 如果执行异步操作失败,则调用reject();这个函数,Promise实例的[PromiseState]的状态为__rejected__(失败),

​ 5> 失败时调用then()方法更改Promise的值[PromiseResult]的值,并执行失败的回调,返回一个新的Promise对象__(链式调用的核心)__。

图解

在这里插入图片描述

使用:- 需求:要按照顺序调用引入,a,b三个文件

// 常规解决方法-----回调
// fs.readFile('./a.txt', function (err, date) {
//     if (err) {
//         return console.log('读取错误');
//     }
//     console.log(date.toString());
//     fs.readFile('./b.txt', function (err, date) {
//         if (err) {
//             return console.log('读取错误');
//         }
//         console.log(date.toString());
//         fs.readFile('./引入.html', function (err, date) {
//             if (err) {
//                 return console.log('读取错误');
//             }
//             return console.log(date.toString());
//         });
//     });
// });

此时可以解决问题,但是如果业务过多会出现维护困难的情况----------回调地狱

  • es6的promise方法------------承诺api

    function rand(url) {//封装一个读取文件的方法(有将异步处理为同步的能力)
        //创建一个promise容器
        // promise容器一旦创建就会执行里面的代码
        let p = new Promise(function (reslove, reject) {
            // 一开始容器中的代码有一个状态为pending(进行中)
            fs.readFile(url, function (err, date) {//读取文件 
                if (err) {//读取失败
                    // 此时将容器内代码的执行状态改为失败
                    // 并将这个状态通过reject来传递出去
                    reject(err);
                }
                // 读取成功
                // 此时容器内执行状态改为成功
                // 并将这个状态通过reslove来传递出去
                reslove(date);
            });
        });
        return p;
    };
    
    rand('./a.txt')//调用方法并传入路径
        // then()方法有针对两种情况的处理方式
        .then((date) => {//成功
            console.log(date.toString());
            return rand('./b.txt');//返回一个promise对象给下一个then-----只有当这个then执行完成才能输出下一个then接收的结果
        }, (err) => {//失败
            return console.log(err);//失败的情况下不会执行下一个then()
        })
        .then((date) => {
            console.log(date.toString());
            return rand('./引入.html');
        }, (err) => {
            return console.log(err);
        }).then((date) => {
            console.log(date.toString());
        }, (err) => {
            return console.log(err);
        });
    

4-5 Promise Apis

4-5-1 Promise 构造函数 :Promise(exctor){}
  • executor 函数 : 执行器 (resolvereject) => {}

​ executor 创建实例时传入的函数参数,本身接受两个参数

  • resolve 函数:内部定义成功时我们调用的函数 value => {}
  • reject 函数:内部定义失败时我们调用的函数 reason =>{}

说明:executor 会在 Promise 内部立即同步调用,异步操作在执行器中执行

4-5-2 Promise.prototype.then 方法 :(onResolved,onRejected)=> {}

  • onResolved 函数:成功回调的函数 (value) => {}

    then函数所接收的第一个参数

  • onRejected 函数:失败的回调函数 (reason) => {}

    then函数所接收的第二个参数

说明:指定用于得到成功 value 的成功回调和用于得到失败 reason 的失败回调,返回一个新的Promise对象

4-5-3 Promise.prototype.catch 方法: (onRejected) => {}

  • onReject 函数:失败的回调函数 (reason) => {}

当函数链式调用失败时失败对象会进入此函数内

4-5-4 Promise中的方法

  • Promise.resolve();方法:(value) => {}

​ value : 成功的数据或Promise对象

注意:此方法返回一个成功或失败的Promise对象

let p2 = Promise.resolve('123');
console.log(p2);
//Promise { '123' }

//在Promise.resolve方法传参中如果传入的参数为非Promise的对象,
//则返回成功的Promise对象
// 如果传入的是Promise对象实例,那么返回的值是根据参数中promise对象的结果而定----套娃

Promise.resolve(new Promise(
    (resolve, reject) => {
        let falg = false;
        if (falg) {
            resolve('成功值');
        } else {
            reject('失败值');
        }
    }
)).then(
    (value) =>{
        console.log(value);
        //成功值
    },

    (reason) => {
        console.log(reason);
        //失败值
    }
)
  • Promise.reject(); 方法:(reason) => {}

    ​ reason : 失败的原因

注意:返回的是一个失败的Promise对象

let p2 = Promise.reject('123');
// console.log(p2);
//Promise { <rejected> '123' }

//在Promise.reject方法传参返回失败的Promise对象(一直返回失败)

Promise.reject(new Promise(
    (resolve, reject) => {
        let falg = true;
        if (falg) {
            resolve('success');
        } else {
            reject('fuile');
        }
    }
)).catch
(
    (reason) => {
        console.log(reason);
        //失败值
    }
)

Promise { 'success' }
(node:14092) UnhandledPromiseRejectionWarning: 123
(Use `node --trace-warnings ...` to show where the warning was created)
(node:14092) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 1)
(node:14092) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
  • Promise.all()方法:(Promise) => {}

    其中的Promise参数:包含多个Promise对象的数组

    说明:返回一个新的Promise,当数组中所有的Promise结果为完成时,最终的结果才为完成

// - 3 Promise.all()方法

let p3 = new Promise((resolve, reject) => {
    resolve('第一个成功值');
});

let p4 = new Promise((resolve, reject) => {
    resolve('这是第二个成功值');
});

let p5 = new Promise((resolve, reject) => {
    resolve('这是第3个成功值');
});

Promise.all([p3, p4, p5]).then((value)=>{
    console.log(value);
});

let p6 = new Promise((resolve, reject) => {
    resolve('第一个成功值');
});

let p7 = new Promise((resolve, reject) => {
    reject('这是第二个失败');
});

let p8 = new Promise((resolve, reject) => {
    resolve('这是第3个成功值');
});

Promise.all([p6, p7, p8]).then((value)=>{
    console.log(value);
});

// (node:7824) UnhandledPromiseRejectionWarning: 这是第二个失败
  • Promise.race()方法:(Promise) => {}

    其中的Promise参数:包含多个Promise对象的数组

    说明:返回一个新的Promise,第一个完成的Promise的状态就是是最终的状态,最终的结果才为完成

let a1 = new Promise((resolve, reject) => {
    reject('这是第一个失败值');
});

let a2 = new Promise((resolve, reject) => {
    resolve('这是第二个成功值');
});

let a3 = new Promise((resolve, reject) => {
    reject('这是第三个失败值');
})

Promise.race([a1, a2, a3]).then((value) => {
    console.log(value)
}, (reason) => {
    throw new Error(reason)
});
//Error: 这是第一个失败值

4-6 Promise的几个关键问题

4-6-1 如何改变Promise的状态?

​ 1> resolve(value):如果当前是pending就会变为resolved

​ 2> reject(reason):如果当前是rending就会变为rejected

​ 3> 抛出异常:如果当前是pending就会变为rejected

4-6-2 一个promise指定多个成功/失败回调函数,都会调用吗?

当Promise改变为对应状态时都会调用

4-6-3 改变 promise状态和指定回调函数后谁先谁后?

​ 1> 都有可能,正常情况下时先指定回调再改变状态,但也可以先改变状态在指定回调

​ 2> 如何先改变状态后指定回调?

		- 在执行器中直接调用 resolve() / reject()
		- 延迟更长时间才调用then()

涉及到微任务与宏任务的执行顺序

​ 3> 什么时候能得到数据?

  • 如果先指定回调,当状态发生改变的时候,回调函数就会调用,得到数据
  • 如果先改变状态,那当指定回调时,回调函数就会调用,得到数据

4-6-4 promise.then()返回新的promise结果状态由什么决定?

​ 1> 简单表达:由then()指定的回调函数执行的结果决定

​ 2>详细信息表达 :

  • 如果抛出异常,新promise变为rejected,reason为抛出的异常

    let b1 = new Promise((res, rej) => {
        rej('成功');
    })
    
    b1.then(
        value => {
          console.log(value);
        },
        
    	reason => {
            console.log(reason)
       		throw '出错了';
    	}
    );
    /*
    (node:15316) UnhandledPromiseRejectionWarning: 出错了
    */
    
  • 如果返回的是非promise值,新的promise值变为resolved,value为返回的值

  • 如果返回的是另一个新promise,此promise的结果就会成为新promise的结果

4-6-5 promise如何串联多个操作任务?

​ 1> promise的then()返回一个新的promise,可以开成then()的链式调用

​ 2> 通过then的链式调用串联多个同步/异步任务

4-6-6 promise 异常穿透?

​ 1 > 当使用 promise 的 then 链式调用时,可以在最后指定失败的回调

​ 2 > 前面任何操作处理异常,都会传递到最后失败的回调中处理

4-6-7 中断promise 链?

​ 1 > 当使用 promise 的链式调用时,在中间中断,不再调用后面的回调函数

​ 2> 办法:在回调函数中返回一个pending状态的promise对象


5 Async 和 Await

5-1 Async

​ 0 用来标志函数形成async函数

​ 1 函数的返回值是promise对象

​ 2 promise 对象的结果由async函数执行的返回值决定

  • 1 返回的一个非promise值

    let d1 = async function done_one() {
        return 521;
    }
    
    console.log(d1());
    //Promise { 521 }
    //只要返回的结果是非promise对象,那么返回得到的永远是一个成功的promise对象
    
  • 2 返回promise对象

    let d2 = async function done_two() {
        return new Promise((resolve, reject) => {
            if (true) {
                resolve('成功');
            } else {
                reject('失败');
            }
        });
    }
    console.log(d2());
    //Promise { <pending> }
    // 返回的结果是promise对象,那么得到是对应成功/失败的promise对象
    
  • 3 抛出错误

    let d3 = async function done_three() {
        return new Promise((resolve, reject) => {
           throw '抛出'
        });
    }
    console.log(d3());
    //Promise { <pending> }
    // 返回的结果是异常,那么得到是失败的promise对象
    
    

5-2 await

  • 1 await 右侧的表达式一般为 promise对象,但也可以是其他的值

  • 2 如果表达式是promise对象,await返回的是promise成功的值

    async function two() {
        let d2 = await new Promise((resolve, reject) => {
            resolve('成功123');
            // reject('失败456');
        })
        return d2;
    }
    
    console.log(two());
    //Promise {<pending>}
    // [[Prototype]]: Promise
    // [[PromiseState]]: "fulfilled"
    // [[PromiseResult]]: "成功123"
    
    // or
    
    // Promise {<pending>}
    // [[Prototype]]: Promise
    // [[PromiseState]]: "rejected"
    // [[PromiseResult]]: "失败456"
    
  • 3 如果表达式是其他的值,直接将此值作为await的返回值

    async function one(){
        let d1 = await 123;
        return d1;
    }
    
    console.log(one());
    //Promise {<pending>}
    // [[Prototype]]: Promise
    // [[PromiseState]]: "fulfilled"
    // [[PromiseResult]]: 123
    

5-3 注意

  • await必须写在async函数中,但是async函数可以没有await

  • 如果await的promise失败了,就会抛出异常,需要通过try......catch....捕获处理

    async function two() {
        try {
            let d2 = await new Promise((resolve, reject) => {
                resolve('成功123');
                // reject('失败456');
            })
            console.log(d2);//成功123
            return d2;
        } catch (e) {
            throw new Error(e);//Error: 失败456
        }
    }
    
    two();
    

5-4 async和await结合使用

 class Time1 {
     fn1(num, timeout) {
         return new Promise((resolve) => {
             setTimeout(() => {
                 resolve(`我是文件${num}`)
             }, timeout);
         });
     };
 };


 const s1 = new Time1();
 const s2 = new Time1();
 const s3 = new Time1();

 // 使用async函数可以处理promise返回的promise对象
 // 其中的await相当于then的作用
 async function asc() {

     // 当每次执行到await时,程序会等到上一次的promise有返回值才开始执行下面的代码

     let data1 = await s1.fn1(1, 3000);
     console.log('------------------------');
     console.log(data1);
     let data2 = await s2.fn1(2, 1000);
     console.log('------------------------');
     console.log(data2);
     let data3 = await s3.fn1(3, 2000);
     console.log('------------------------');
     console.log(data3);
     let data = [data1, data2, data3];
     return data;
 };

 asc().then((data) => {
     // 执行完成后的data接收的是async的成功返回值(promise)
     console.log('执行完毕', data);
 }).catch((err) => {
     // 当三个执行步骤一个出现问题流程将不再进行下去并抛出错误
     console.log(err);
 })
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值