一、什么叫异步?
在JS中有同步和异步两种模式
1、同步(Synchronous)
- 一般指后一个任务等待前一个任务结束,程序的执行顺序与任务的排列顺序是一致的
2、异步(Asynchronous)
- 每一个任务有一个或多个回调函数,前一个任务结束后,不是执行后一个任务,而是执行回调函数,后一个任务则是不等前一个任务·结束就执行;所以程序的执行顺序与任务的排列顺序是不一致的;
- 另一种简单的解释:一个任务分两段,先执行第一段,再执行其他任务;当第一段有执行结果时,回去执行第二段
二、JS中为什么要设计异步??
JS是单线程,设计异步可以提高CPU的利用率;但增加了开发难度
1、JS为什么要设计成单线程???
单线程:即同一时间只能做一件事情;JS作为脚本语言,主要的作用是与用户互动、操作DOM,这决定了JS只能是单线程的;否则会带来很复杂的同步问题;
比如:当JS是多线程的,存在有两个线程A、B,线程A想要删除dom1,线程B想要在dom1添加内柔,此时就出现了冲突,不知道应该执行哪个线程的操作。
HTML5提出Web Worker标准允许脚本创建多个线程,但是子线程完全受主线程的控制且不得操作DOM;因为该新标准并没有改变JS单线程的本质
三、事件循环 Event Loop
主线程:JS的首要任务,可以理解为同步任务,先进先出;
执行栈:执行栈是一个先入后出的数据结构,一个同步任务来了进入栈底马上执行它,然后释放;
任务队列:任务队列的存在是为了应付一些异步任务,在执行一个任务如何迟迟等不到不能一直等待,必须让程序接着往下进行;任务队列用于存放这些任务;先进先出;
宏任务:较常见的:script全部代码、setTimeout、setInterval、setImmediate、I/O、UI Rendering
微任务:较常见的:Process.nextTick、Promise(new Promise的过程属于同步任务,resove或者reject之后才算微任务)
Event Loop的流程
- 主线程首先按照顺序执行同步任务,将所有同步任务处理完;
- 查看微任务队列里是否存在微任务,若存在;将所有微任务队列按顺序执行掉;
- 查看宏任务队列是否存在宏任务,若存在先把第一个宏任务执行掉;
- 一直重复步骤2 、3,直到所有微任务和宏任务执行完毕
console.log('script start');//同步任务setTimeout(function(){
console.log('setTimeout');//宏任务},0);
Promise.resolve().then(function(){
console.log('promise1');//微任务}).then(function(){
console.log('promise2');//微任务});
console.log('script end');//同步任务//根据事件循环规则,结果是:script start、script end、promise1、promise2、setTimeout
四、实现异步的方式
ES6诞生以前,异步编程的方法大概有:回调函数、事件监听、发布/订阅、Promise对象
ES6诞生之后,Generator函数将异步编程带入了一个新阶段
1、回调函数
异步编程最基本的方法就是回调函数;回调函数是一段可执行的代码段,它作为一个参数传递给其他代码,其作用是在需要的时候方便调用这段代码(回调函数)。“作为参数传递到另一个函数中,这个参数的函数就是回调函数”
特点:
- 不会立即执行:回调函数在调用函数数中也要通过()运算符调用才会执行
- 是一个闭包
- 执行前会判断是否是一个函数
functionadd(num1, num2, callback){var sum = num1 + num2;if(typeof callback ==='function'){//callback是一个函数,才能当回调函数使用callback(sum);}}functionprint(num){
console.log(num);}add(1,2, print);//3
关于回调函数this指向问题
回调函数调用时this的执行上下文并不是回调函数定义时的上下文;而是调用它的函数所在的上下文;
当回调函数是普通函数时,当做参数传入另外的函数时,若不知道这个函数内部怎么调用回调函数,就会穿线回调函数中this指向不明确的问题;
functioncreateData(callback){callback();}var obj ={data:100,tool:function(){createData(function(n){
console.log(this);//window//this指向是离它最近的或者嵌套级别的function的调用者})}}
obj.tool();
解决回调函数this指向的方法
- 箭头函数 把箭头函数当做回调函数,作为参数传入另外一个函数中就不会出现上述代码中this指向不明的问题
functioncreateData(callback){callback();}var obj ={data:100,tool:function(){createData((n)=>{this.data = n;//this是obj})}}
obj.tool();
- 记录this
functioncreateData(callback){callback(999);}var obj ={data:100,tool:function(){var self =this;//这里的this指向obj,然后当一个变量取用createData(function(n){
self.data = n;})}}
obj.tool();
2、promise对象
Promise对象是CommonJS提出来的一种规范,目的是为了异步编程提供统一接口
中心思想:每一个异步任务返回一个promise对象,该对象有一个then方法,允许指定回调函数
特点:(1)对象状态不受外界影响;(2)一旦状态改变看就不会在变化;任何时候promise都只有一种状态
(1)promise的状态
- Pending:进行中
- Resolved:已完成,promise从Pending状态开始,如果成功就转到成功态,并执行resolve回调函数
- Rejected:已失败,promise从Pending状态开始,如果成功就转到失败态,并执行reject回调函数
(2)基本使用
通过Promise构造函数创建promise对象,Promise构造函数的参数是一个函数,该函数的两个参数是
resolve,reject
。
当Promise对象状态变为
Resolved
,则调用
resolve
并将操作结果作为其参数传递出去;
当Promise对象状态变为
Rejected
,则调用
reject
并将操作报出的错误作为其参数传递出去;
var promise =newPromise(function(resolve,reject)//一些操作});
(3)then方法和catch方法
- then方法有三个参数:成功回调、失败回调、前进回调;较常用的是只传第一个参数:成功回调 Promise执行then后还是Promise,因此可以链式调用;并且Promise的执行是异步的
- catch方法有一个参数:失败回调;
.catch(onRejected)
相当于.then(undefined, onRejected)
;它返回了一个失败的promise以及返回一个参数作为一个失败理由。promise的错误抛出后会一直传递到最外层直到被捕获;当catch捕获错误以后,返回的还是一个promise对象,可以继续链式调用
newPromise(function(resolve,reject){
console.log('hi~~~~~')//这是同步代码resolve("hello world");reject('error')}).then(v=>{
console.log('成功回调',v);//打印resolve传递的参数 hello world },e=>{
console.log('失败回调',e);//打印reject传递的参数 error})//相当于newPromise(function(resolve,reject){
console.log('hi~~~~~')//这是同步代码resolve("hello world");reject('error')}).then(v=>{
console.log('成功回调',v);//打印resolve传递的参数 hello world }).catch(e=>{
console.log('失败回调',e);//打印reject传递的参数 error})
(4)all方法
- Promise.all 生成并返回一个新的 Promise 对象;参数传递promise数组中所有的 Promise 对象都变为resolve的时候,该方法才会返回, 新创建的 Promise 则会使用这些 promise 的值。
- 如果参数中的任何一个promise为reject的话,则整个Promise.all调用会立即终止,并返回一个reject的新的Promise 对象
var p1 = Promise.resolve(1)var p2 = Promise.resolve({a:2})var p3 =newPromise(function(resolve,reject){setTimeout(function(){resolve(3)},3000)})
Promise.all([p1,p2,p3]).then(result=>{// 返回的结果是按照Array中编写实例的顺序来
console.log(result)// 1 {a:2} 3})
(5)race方法
“赛跑”,接收一个promise对象数组为参数;只要有一个promise对象进入FulFilled或者Rejected状态的话,就会继续进行后面的处理
// `delay`毫秒后执行resolvefunctiontimerPromisefy(delay){returnnewPromise(resolve=>{setTimeout(()=>{resolve(delay);}, delay);});}// 任何一个promise变为resolve或reject的话程序就停止运行
Promise.race([timerPromisefy(10),timerPromisefy(32),timerPromisefy(64)]).then(function(value){
console.log(value);// => 10});//在第一个变为确定状态的10ms后,.then注册的回调函数就会被调用
(6)finally函数
ES9 新增,finally() 方法返回一个Promise。在promise结束时,无论结果是fulfilled或者是rejected,都会执行指定的回调函数。
3、发布/订阅
发布订阅模式是完全解耦的,发布者、消息中心、订阅者三方的业务逻辑完全独立的,之间仅通过消息来传递。在观察者模式中,被观察者(Subject)只需要一套观察者的集合(Observer),将有关状态的任何变更自动通知给观察者(watcher),这个设计是松耦合的。
4、生成器函数 Generator/yield
协程:多个线程互相协作,完成异步任务
协程的运行流程:
1、协程A开始执行;
2、协程A执行到一半,进入暂停状态,执行权转移到协程B;
3、一段时间后,协程B将执行权归还
4、协程A恢复执行
协程的Generator函数实现:
function*gen(x){let y =yield x+2return y
}let g =gen(1)
g.next()// {value:3, done:false}
g.next()//{value:undefiend, true}
Generator函数最大的特点就是交出函数的执行权,整个Generator函数就是一个封装的异步任务,或者说是异步任务的容器。调用Generator函数会返回一个内部指针(即遍历器);调用指针的next方法可以移动内部指针,指向第一个遇到yield语句。
换言之.next方法的作用是分阶段执行,每次调用next方法会返回一个对象,表示当前阶段的信息;value:yield语句后面表达式的值,表示当前阶段的值;done属性:一个布尔值,表示Generator函数是否执行完毕,即是否还有下一个阶段
异步任务的封装:
let fetch =require('node-fetch');function*gen(){let url ='https://api.github.com/users/github';let result =yieldfetch(url)}let g =gen()//首先执行Generator函数获取遍历器对象let result = g.next()//执行异步任务的第一阶段//由于fetch返回的是一个promise,因此可以调用.then方法
result.value.then((data)=>{return data.json()}).then(()=>{
g.next(data)})
5、async和await函数
ES2017标准引入了async函数; 它是Generator的语法糖,async函数就是将Generator函数的(*)替换成了async,将yield替换成了await。
async是异步的意思,而 await 是等待的意思,await 用于等待一个异步任务执行完成的结果;建立在promise的基础上。async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结果;async函数的返回值是promise对象,可以使用then方法添加回调函数。
functiontimeout(ms){returnnewPromise(resolve=>{setTimeout(resolve, ms)})}asyncfunctionasyncPrint(value, ms){
awiat timeout(ms)//正常情况下,await命令后面是一个promise;如果不是会被转成一个立即resolve的promise对象
console.log(value)}asyncPrint('你好',50)//50ms后打印'你好'
版权归原作者 蔣以夢 所有, 如有侵权,请联系我们删除。