期约与异步

同步与异步

同步行为和异步行为是计算机科学的一个基本概念,可以理解为程序执行的两种模式;

在JavaScript这种 单线程事件循环模型 中,异步是十分重要的,异步行为可以优化计算量大导致的运行事件长的操作,它可以在等待其他操作完成的同时,即使运行其他指令,系统也能保持稳定

而异步行为也并非只针对那些计算量大或者运行事件长的,只要你不想因为等待某个操作而阻塞线程执行,那么都可以使用异步行为来优化

1)同步行为

在同步行为下,每条指令都会严格按照代码出现的顺序来执行,而每条指令执行后也能立即获得存储在系统本地(如寄存器或系统内存)的信息。

程序执行的每一步,都可以推断出程序的状态。这是因为后面的指令总是在前面的指令完成后才会执行。同步代码可以轻易的分析出程序在执行到任意代码位置时的状态。

2 ) 异步行为

异步行为类似于系统中断,即当前进程外部的实体可以触发代码执行。异步操作经常是必要的,因为强制进程等待一个长时间的操作通常是不可行的(同步操作则必须要等)。如果代码要访些高延迟的资源,比如向远程服务器发送请求并等待响应,那么就会出现长时间的等待,所以的请求都会封装成异步操作。 相反的,我们可以将“异步”看成是“不同步伐的执行代码,且异步代码是在不同的跑道那么指令的执行顺序当然是不可预知的。

let x=3;
setTimeout(()=>x=x+4,1000);
//一秒后将箭头函数推入到执行流的最后端

在上段代码中,第1排和第2排是同步代码,但是第2排中的箭头函数是异步代码。或者说是一个回调的过程,在1秒钟以后,将该函数推入事件流执行的队列中去。

第二个指令块(加操作及赋值操作)是由系统计时器触发的,这会生成一一个入队执行的中。到底什么时候会触发这个中断,这对JavaScript运行时来说是一个黑盒, 因此实际上无法预知

为了让后续代码能够使用X,异步执行的函数需要在更新x的值以后通知其他代码,在早期的JavaScript中,只支持定义回调函数来表现异步操作完成。

let x = 3;
setTimeout(()=>{
x=x+4;
console.log(x);
})

异步返回值

假设 setTimeout 操作会返回一个有用的值。有什么好办法把这个值传给需要它的地方?广泛接受的一个策略是给异步操作提供一个回调,这个回调中要包含 ”使用异步返回值 的代码”(作为回调的参数)

function test(name){
    setTimeout(()=>{
           let userName = name + ",你好!";
           return userName;
    })
   }
   var a = test("张三");
   console.log(a);
​


   function test(name,fun){
    setTimeout(()=>{
           let userName = name + ",你好!";
           fun(userName);
    })
    }
    test("张三",acy);
    function acy(userName){
    console.log(userName);
    }
​

在JS中,异步的特性并没有我们想象的那么简单,之前我们讲到:关于 JavaScript 单线程事件循环模型的特性,那么在JS代码运行时,第一轮执行的同步代码永远无法获取到回调时操作,或返回的数据

JavaScript 是单线程的,所以每次只能执行一段代码。为了调度不同代码的执行,JavaScript 维护了一个任务队列。其中的任务会按照添加到队列的先后顺序执行。setTimeout()的第二个参数只是告诉 JavaScript 引擎在指定的毫秒数过后把任务添加到这个队列。如果队列是空的,则会立即执行该代码。如果队列不是空的,则代码必须等待前面的任务执行完才能执行。

   function test(name){
    setTimeout(()=>{
           let userName = name + ",你好!";
           return userName;
    })
   }
   var a = test("张三");
   console.log(a);      //输出 undefined

回调地狱

如果异步返值又依赖另一个异步返回值,那么回调的情况还会进一步变复杂。在实际的代码中,这就要求嵌套回调

var num = 4;
            setTimeout(() => {
                num--;
                console.log("1秒后:" + num);
                setTimeout(() => {
                    num--;
                    console.log("1秒后:" + num);
                    setTimeout(() => {
                        num--;
                        console.log("1秒后:" + num);
​
                    }, 1000);
                }, 1000);
            }, 1000);

期约

期约的定义,使用

Promise是ES6新增的一个内置类(不兼容IE浏览器),我们叫它期约,或者约定,和容类语言类似,意思是对某个现阶段不存在的对象进行约定操作

promise是一种设计模式(承诺者模式)

该设计模式的组成:立承诺==》根据承诺执行动作

要干什么事情,状态(成功,失败),成功/失败后对应要做的事情,后续需要做的上去按照之前的承诺去执行进行了

实现步骤

1.设立一个承诺:也就是创建了一个promise实例

2.在promise实例中,传递的初始必须是一个函数,否则会报错,

(该函数是一个可执行函数,一般是一段用于管理异步。)

3、在 “new Promise” 时,会将传递进去的函数立即执行

4、该函数中有两个行参(resolve&reject),这两个参数的参数类型是新的函数

5、为了在需要的时候才去创建Promise对象,我们一般将创建Promise对象的代码封装在一个函数中。

then()方法

保证匿名内部函数的代码运行完后再执行后面的代码

then()是为期约实例添加处理程序的主要方法

var num = 4;
            setTimeout(() => {
                num--;
                console.log("1秒后:" + num);
                setTimeout(() => {
                    num--;
                    console.log("1秒后:" + num);
                    setTimeout(() => {
                        num--;
                        console.log("1秒后:" + num);
​
                    }, 1000);
                }, 1000);
            }, 1000);

then方法接收的两个参数实际上是针对期约的结果而执行的处理程序。分别是:onResolved(成功) 处理程序和onRejected(失败) 处理程序 1、这两个参数都是可选的,如果提供的话,则会在期约分别进入“兑现”和“拒绝”状态时执行。

2、当 promise 期约实例返回的结果为 resolve(兑现)时,执行then方法的onResolved 处理程序(即then的第一个参数)

3、当 promise 期约实例返回的结果为 reject(解决)时,执行then方法的onRejected 处理程序(即then的第二个参数)

        var pro1 = new Promise((resolve,reject)=>{resolve()});
        var pro2 = new Promise((resolve,reject)=>{reject()});
        function onReslved(){
            console.log("期约允许");
        }
        function onRejected(){
            console.log("期约拒绝");
        }
        pro1.then(()=>{onReslved()},()=>{onRejected()});
        pro2.then(()=>{onReslved()},()=>{onRejected()});

then()一定要在期约有响应的状态下执行;

期约的三种状态

待定pending

允许resolved/fulfilled

拒绝rejected

注:这里的兑现、拒绝并不是指程序运行上的成功或失败(报错),而只是指程序逻辑上的成功或失败。

(比如需要返回1:返回成功resolved,返回2失败rejected)

        var pro1 = new Promise((resolve,reject)=>{resolve()});
        var pro2 = new Promise((resolve,reject)=>{reject()});
        function onReslved(){
            console.log("期约允许");
        }
        function onRejected(){
            console.log("期约拒绝");
        }
        pro1.then(()=>{onReslved()},()=>{onRejected()});
        pro2.then(()=>{onReslved()},()=>{onRejected()});

期约状态、then联用:当期约实例中的状态函数被调用时,还会触发then方法的调用。并且还可以给then中的方法传递参数使用

链式操作

通过链式操作,我们可以连续的对期约求值,按顺序的进行异步操作。同样要使用到 then 方法。

例:我们在商城购买东西时,一般需要经过一下3个步骤:选择商品、下单购买、收快递。不管单独动作的执行时间是多少,但这一系列动作一定是有先后顺序的。

 function task1(){
                return new Promise(function(resolve,reject){
                    setTimeout(function(){
                        console.log("选择商品");
                        resolve(["鞋子","耐克"]);
                    },3000);
                });
            }
            function task2(){
                return new Promise(function(resolve,reject){
                    setTimeout(function(){
                        console.log("下单商品");
                        resolve("1299");
                    },3000);
                });
            }
            function task3(){
                return new Promise(function(resolve,reject){
                    setTimeout(()=>{
                        console.log("取快递");
                        resolve({name:'张三',tal:'123456'})
                    },3000)
                })
            }
            // task1().then(task2().then(task3().then()))
            task1().then(function(data){
                console.log("您选择了商品");
                console.log(data);
                return task2();
            }).then(function(data){
                console.log("你支付了¥");
                console.log(data);
                return task3();
            }).then(function(data){
                console.log("收取快递信息为");
                console.log(data)
            });
            console.log(a);
            var b = newPro(-2);
            console.log(b);
            var num = 4;

catch()方法

与“try......catch”语法类似,promise对象的catch方法也是用来捕获异常的。

catch方法更多的是使用在链式操作上,我们知道异步代码的报错是不会影响到第一轮的同步代码的。但是,当我们通过链式语法操作多个异步代码时,那么前一个期约的报错,会导致后面的期约停止运行。

在一节链式操作执行完以后,可以为该段链式进行异常捕获,在后面调用catch()方法即可。

同样,catch方法也接受一个内置的匿名方法,作为报错后执行的操作。

all()方法

Promise.all() 静态方法创建的期约会在一组期约全部解决之后再解决。这个静态方法接收一个可迭代对象,返回一个新期约 ​ 通过all创建的合成期约,必须等到所有内部期约都执行成功后,才会执行。如果所有期约都成功解决,则合成期约的解决值就是所有内部期约解决值的数组,且按照原本迭代器顺序返回。 ​ 如果有期约拒绝,则第一个拒绝的期约会将自己的理由作为合成期约的拒绝理由。之后再拒绝的期约不会影响最终期约的拒绝理由。(也就是只有最先出现的内部期约拒绝理由会展示到外部,其余的拒绝理由不会展示),但是其他内部拒绝理由任然会执行,只不过不展示到外部。

function wash(){
                return new Promise(function(resolve,reject){
                    setTimeout(()=>{
                        console.log("洗杯子");
                        return resolve("杯子");
                    },1000)
                })
            }
            function boil(){
                return new Promise(function(resolve,reject){
                    setTimeout(()=>{
                        console.log("烧开水");
                        return resolve("开水");
                    },2000)
                })
            }
            function caty(){
                return new Promise(function(resolve,reject){
                    setTimeout(()=>{
                        console.log("拿茶叶");
                        return resolve("茶叶");
                    },3000);
                })
            }
            Promise.all([wash(),boil(),caty()]).then(function(a){
                console.log(a)
            })

race()方法

Promise.race()静态方法返回一个包装期约,是一组集合中最先解决或拒绝的期约的镜像。这个方法接收一个可迭代对象,返回一个新期约。

该方法返回的新期约,只会对第一个出现反应的内部期约有效果(无论状态)。同样的,其他内部期约还是会执行,只不过不会影响到外部期约。 与all相反,race方法只要有一个内部期约有结果,那么外部新期约就会执行

   function run(){
                let num = Math.ceil(Math.random() * 10);
                console.log("我是"+num+"选手")
                return new Promise((resolve,reject)=>{
                setTimeout(()=>{
                    console.log("我跑了"+num+"秒")
                    resolve(num);
                },num*1000);
                });
            }
            Promise.race([run(),run(),run()]).then(function(num){
                console.log("第一名是:"+num);
            });

异步函数

async/await 旨在解决利用异步结构组织代码的问题。为此,ECMAScript 对函数进行了扩展,为其增加了两个新关键字:async 和 await

async

关键字用于声明异步函数。这个关键字可以在函数声明,函数表达式,箭头函数和方法上。

使用async关键字可以人函数具有异步特征,但不会进行异步的操作,总体上其代码仍然同步求值

而在参数或闭包方面,异步函数仍然具有普通 JavaScript 函数的正常行为。(与普通代码在运行上并无区别)

1、没有使用 return 关键字:没有使用 return 关键字会返回一个 undefined,只不过和普通的函数返回 undefined 不也一样,他是返回一个 Promise 对象,只不过对象“兑现”内容为空。

2、使用 return 关键字返回值:这个值会被 Promise.resolve()包装成一个期约对象。异步函数始终返回期约对象。在函数外部调用这个函数可以得到它返回的期约。

await

异步函数数主要针对不会马上完成的任务,所以自然需要一种暂停和恢复执行的能力。使用 await关键字可以暂停 异步函数代码 的执行,等待期约解决。 ​ await 关键字会暂停执行 关键字 后面的异步代码,让出 JavaScript 运行时的执行线程。(简单的理解为:会在解析到await关键字时停止执行,并让出线程)

真正可以体现异步函数作用的就是 “await” 关键字,“async” 关键字只是一个用于声明异步的标识。

await 关键字的用法与 JavaScript 的一元操作一样。它可以单独使用,也可以在表达式中使用

  async function foo() {
                console.log(2);
                console.log(await 4);
                
​
            }console.log(1);
            foo();
            console.log(3);

只有当 await关键字 解析到一个值时,才会有效

await 关键字,它并非只是等待一个值可用那么简单。JavaScript 运行时在碰到 await 关键字时,会记录在哪里暂停执行。等到 await 右边的值可用了,JavaScript 运行时会向消息队列中推送一个任务,这个任务会恢复异步函数的执行。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值