JavaScript--详解异步编程

本文详细探讨了JavaScript中的同步与异步模式,重点解析了Promise的概念、基本用法、常见误区以及Promise链式调用。此外,还介绍了Generator函数在异步方案中的应用,提供了一种更优的异步编程体验。最后,简要提及了手写Promise的核心逻辑和源码分析。
摘要由CSDN通过智能技术生成

众所周知,目前主流的JavaScript环境都是以单线程模式去执行的JavaScript代码。JavaScript采用单线程模式工作的原因,与它的设计初衷有关。最早JavaScript这门语言是运行在浏览器端的脚本语言,目的是为了实现页面的的交互效果,页面的交互核心就是DOM操作,这就决定了它必须使用单线程模型,否则会出现很复杂的线程同步问题。

单线程
JS执行环境中负责执行代码的线程只有一个。同一时间,只有一个任务能运行,其他任务必须排队,容易造成阻塞。

JavaScript将任务的执行模式分成了两种:

  • 同步模式(Synchronous)
  • 异步模式(Asynchronous)

一、同步模式与异步模式

1、同步模式

同步模式

  • 代码当中的任务依次执行,后一个任务必须要等待前一个任务执行结束才能够开始执行。
  • 程序的执行顺序跟我们代码的编写顺序是完全一致的。
  • 在单线程模式下,我们大多数任务都会以同步模式去执行。
  • 同步指的不是同时执行,而是排队执行。

调用栈

  • JS在执行引擎当中维护了一个正在工作的工作表,或者说正在执行的一个工作表,在这个里面会记录当前我们正在做的一些事情,当这个工作表当中所有的任务全部被清空过后,那这一轮的工作就算是结束了。

排队执行的机制存在的问题

  • 如果说其中的某一个任务,或者具体点说某一行代码执行时间过长,那它后面的任务就会被延迟,我们把这种延迟称之为阻塞,这种阻塞对于用户而言,那就意味着界面会有卡顿或卡死。
  • 因此必须要有异步模式来解决我们程序中那些无法避免的耗时操作。例如浏览器端的Ajax操作,Node.js中的大文件读写,都会需要使用到异步模式去执行,从而去避免我们的代码被卡死。
//纯同步模式
console.log('global begin');

function bar() {
   
    console.log('bar task');
}

function foo() {
   
    console.log('foo task');
    bar()
}

foo()

console.log('global end');

//结果
// global begin
// foo task    
// bar task    
// global end 

2、异步模式

(1.)异步模式
  • 异步模式的api是不会去等待这个任务的结束才开始下一个任务。
  • 对于耗时操作,它都是开启过后就立即往后执行下一个任务,耗时操作的后续逻辑一般会通过回调函数的方式去定义,那在内部我们这个耗时任务执行完成后,就会自动去执行我们这里传入的回调函数。
  • 异步模式对于JavaScript十分重要,因为如果没有这种模式的话,我们单线程的JavaScript语言就无法同时处理大量耗时任务。
  • 对于开发者而言,单线程下面的异步,它最大的难点就是代码执行顺序混乱,不会像同步代码的执行顺序通俗易懂。
  • 如果说JS调用栈是一个正在执行的工作表,那消息队列(Queue)就可以理解为一个待办工作表,而JS执行引擎是先去做完调用栈中所有的任务,然后再通过事件循环(Event loop),从消息队列(Queue)当中再取一个任务出来,继续去执行,以此类推,整个过程中我们随时都可以往消息队列当中去放入一些任务,那这些任务在消息队列中会去排队等待事件循环。
  • 异步模式主要是通过内部的消息队列和事件循环去实现的。
//异步模式
console.log('global begin');

setTimeout(function timer1() {
   
    console.log('timer1');
}, 1800);

setTimeout(function timer2() {
   
    console.log('timer2');
    setTimeout(function inner() {
   
        console.log('timer2 inner');
    }, 1000);
}, 1000);

console.log('global end');

//结果
//global begin
//global end
//timer2
//timer1
//timer2 inner
  • 异步模式图解在这里插入图片描述
  • 注意:JavaScript确实是单线程的,但是浏览器并不是单线程的,而更具体点来说,就是我们通过JavaScript调用的某些内部API,它并不是单线程的,例如倒计时器。
  • 我们所说的同步异步模式,并不是指我们写代码的方式,而是说运行环境提供的API是以同步或异步模式的方式去工作。
  • 同步模式API的特点就是这个任务执行完代码才会继续往下走,例如console.log,而异步模式的API它就是下达这个任务开启过后的指令,就会继续往下执行,代码是不会在这一行等待任务的结束的,例如setTimeout。
(2.)回调函数
  • 它是JavaScript实现异步编程的一种方式,同时也是所有异步编程方案的根基。
  • 回调函数可以理解为一件你想要做的事情。
  • 你明确知道这件事情应该怎么做,怎么样一步一步的往下做,但是你并不知道这件事情所依赖的任务什么时候才能完成,所以说最好的办法就是,把你这件事情的步骤写到一个函数当中,交给任务的执行者,那这个任务的异步执行者他是知道这个任务什么时候结束的,那他就可以在结束过后去帮你执行你想要做的事情,这件想要做的事情就可以理解为回调函数。
    在这里插入图片描述
  • 由调用者定义,交给执行者执行的函数,就被称之为回调函数。
  • 具体用法就是把函数作为参数去传递。
  • 异步编程的整个过程中,执行顺序会非常混乱,代码不易于阅读。
  • 常见的实现异步的方式:传递回调函数参数、事件机制、发布订阅

二、Promise

1、Promise概念

  • 回调函数是所有异步编程方案的根基。但是如果直接使用传统回调方式去完成复杂的异步流程,就无法避免大量的异步函数嵌套,这样就会导致我们常说的回调地狱问题。在这里插入图片描述
  • 为了避免回调地狱的问题,CommonJS社区提出来了Promise的规范,目的就是为异步编程去提供一种更合理更强大的解决方案。在ES2015中被标准化,成为语言规范。
  • Promise是一种更优的异步编程统一方案,是为了解决回调地狱。
  • Promise实际上就是一个对象,用来去表示一个异步任务,最终结束过后它究竟是成功还是失败。在这里插入图片描述

2、Promise基本用法

  • Promise其实就是ECMAScript2015提出的一个全局类型,可以通过new Promise来创建一个Promise示例,即创建一个承诺。
//Promise
const promise = new Promise(function (resolve, reject) {
   
    //这里用于“兑现”承诺
    resolve(100)  //承诺达成
    // reject(new Error('promise rejected'))  //承诺失败
})

promise.then(function (value) {
   
    console.log('resolved', value);
}, function (error) {
   
    console.log('rejected', error);
})

console.log('end');

//结果
//end
//resolved 100
//或者 rejected Error: promise rejected
  • Promise的状态一旦确定过后,就不能再修改了。

3、Promise使用案例

//Promise方式的AJAX
function ajax(url) {
   
    return new Promise(function (resolve, reject) {
   
        var xhr = new XMLHttpRequest()
        xhr.open('GET', url)
        xhr.responseType = 'json'
        xhr.onload = function () {
   
            if (this.status === 200) {
   
                resolve(this.response)
            } else {
   
                reject(new Error(this.statusText))
            }
        }
        xhr.send()
    })
}

//正常测试
ajax('/api/users.json').then(function (res) {
   
    console.log(res);
}, function (error) {
   
    console.log(error);
})
//结果
//[{name: "Rock", age: 5},{name: "Tom", age: 4}]

//异常测试
ajax('/api/bar.json').then(function (res) {
   
    console.log(res);
}, function (error) {
   
    console.log(error);
})
//结果
//GET http://localhost:8080/api/bar.json 404 (Not Found)

4、Promise常见误区

  • 从表象上看,Promise本质也就是使用回调函数方式,去定义异步任务结束后所需要执行的任务,只不过这里的回调函数,是通过then方法传递进去的。而且Promise将我们的回调分成了两种:成功后的回调onFullfilled和失败后的回调onRejected。
  • 嵌套使用的方式是使用Promise最常见的错误。
  • 正确做法是借助于Promise then方法的链式调用的特点,尽可能去保证异步任务的扁平化。

5、Promise链式调用

  • 其实相比于传统回调函数的方式,Promise最大的优势就是可以链式调用。这样就能最大程度的避免回调嵌套。
  • Promise对象的then方法会返回一个全新的Promise对象,目的是为了去实现一个Promise的链条,也就是一个承诺结束之后,再去返回一个新的承诺。每一个承诺都可以负责一个异步任务,相互之间又没有什么影响。
  • 每一个then方法它实际上都是在为上一个then返回的Promise对象,添加状态明确过后的回调(即注册回调)。这些Promise会依次执行,即这里添加的这些回调函数自然也会从前到后依次执行。
//Promise链式
ajax('/api/users.json')
    .then(function (res) {
   
        console.log(1111);
    }).then(function (res) {
   
        console.log(2222);
    }).then(function (res) {
   
        console.log(3333);
    }).then(function (res) {
   
        console.log(4444);
    })
//结果
//1111
//2222
//3333
//4444
  • 而且我们也可以在then的回调当中手动返回一个Promise对象。那么下一个then方法实际上就是在为这个手动的Promise对象去添加状态明确后的回调。即下述代码中的ajax请求执行完成后,会自动去执行下一个then方法当中的回调函数,这样就可以去避免不必要的回调嵌套了。
//手动返回一个Promise对象
ajax('/api/users.json')
    .then(function (res) {
   
        console.log(1111);
        return ajax('/api/urls.json')
    }).then(function (res) {
   
        console.log(2222);
        console.log(res);
    }).then(function (res) {
   
        console.log(3333);
    }).then(function (res) {
   
        console.log(4444);
    })
//结果
//1111
//2222
//{posts: "/api/posts.json",us
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值