js异步编程的三种模式

2022-09-28 14:17:51 浏览数 (2)

写在前面

 javascript语言的执行环境是"单线程"(single thread),就是指一次只能完成一件任务。如果有多个任务,就必须排队,等前面一个任务完成,再执行后面一个任务,以此类推。undefined 这种模式的好处是实现起来比较简单,执行环境相对单纯;坏处是只要有一个任务耗时很长,后面的任务都必须排队等着,会拖延整个程序的执行。

单线程

代码语言:scss复制
function f1() {
    console.log('1')
}
function f2() {
    console.log('2')
}
f1()
f2()

 很容易可以看出,上述代码会依次输出1,2。因为代码是从上到下,依次执行,执行完f1(),才会执行f2()。但是如果f1()中的代码执行的是读取文件或者ajax操作呢,文件的读取都需要一定时间,难道我们需要完全等到文件完全读完再进行写操作么?为了解决这个问题,接下来我们来探究一下js中 同步和异步 的概念。

同步和异步

同步

  • 指在 主线程上排队执行的任务,只有前一个任务执行完毕,才能继续执行下一个任务。
  • 也就是调用一旦开始,必须这个调用 返回结果(划重点——)才能继续往后执行。程序的执行顺序和任务排列顺序是一致的。

异步

  • 异步任务是指不进入主线程,而进入 任务队列的任务,只有任务队列通知主线程,某个异步任务可以执行了,该任务才会进入主线程。
  • 每一个任务有一个或多个 回调函数。前一个任务结束后,不是执行后一个任务,而是执行回调函数,后一个任务则是不等前一个任务结束就执行。
  • 程序的执行顺序和任务的排列顺序是不一致的,异步的。
  • 我们常用的setTimeout和setInterval函数,Ajax都是异步操作。

那么如何实现异步编程呢,笔者介绍几种方法

Web前端视频讲解:进入学习

回调函数(Callback)

回调函数,这是异步编程最基本的方法。

代码语言:javascript复制
const fs = require('fs')
fs.readFile('./pakage.json',(err,info) => {
    fs.writeFile('./p.json',info,(err) => {
        if(!err) {
            setTimeout(() => {
                console.log('ok')
            },2000)
        }
    })
})

上述代码通过回调函数的嵌套,从文件系统中读取一个./pakage.json文件并写入./p.json,读取成功两秒后输出'ok'。用回调来实现异步,没有什么问题。

但是试想,如果再多几个异步函数,代码整体的维护性,可读性都变的极差,如果出了bug,修复过程也变的极为困难,这个便是所谓的 回调函数地狱

Promise对象

Promise对象用于表示一个异步操作的最终状态(完成或失败),以及其返回的值。

MDN对Promise定义如上,Promise本意为承诺,我们可以理解为程序承诺过一段时间后会给你一个结果。

Promise是一个对象,可以保存三个状态 每一时刻必须有一个状态。

  • 成功 Fulfilled
  • 失败 Rejected
  • 处理中 Pending
  • 默认 pending 如果调用 resolve fulfilled
  1. 默认 pending 如果调用 reject rejeced
promise状态promise状态
代码语言:javascript复制
const fs = require('fs')
const promise1 = new Promise((resolve,reject) => {
    fs.readFile('./package.json',(err,info) => {
        resolve(info)
    })
})
const promise2 = (info) => {
    new Promise((resolve,reject) => {
        fs.writeFile('./p.json', info,(err) => {
            if(!err) {
                resolve();
            }else{
                reject();
            }
        })
    })
}
const promise3 = (time) => {
    return new Promise((resolve,reject) => {
        setTimeout(() => {
            resolve()
        },time)
    })
}
//then链式调用
//读文件成功 将结果作为参数传入promise2
promise1.then((info) => {
    return promise2(info)
})
.then(() => {
    // 等着前面的promise 
    console.log('读写完成')
    return promise3(2000)
})
.then( ()=> {
    console.log('ok')
})

 这么一看,并没有什么区别,还比上面的异步回调复杂,得先新建Promise再定义其回调。但其实,Promise的真正强大之处在于它的多重链式调用,可以避免层层嵌套回调。

 我们先使用new来构建一个promise。Promise接受一个函数作为参数,该函数的两个参数分别是resolve和reject。

resolve:成功时调用,并将结果,作为参数传递出去;

reject:失败时调用,并将错误,作为参数抛出。

  • then方法接收两个函数作为参数,第一个参数是Promise执行成功时的回调,第二个 参数是Promise执行失败时的回调。
  • Promise对象的then方法返回一个新的Promise对象,因此所以可以通过链式调用then方法。

我们还可以继续优化一丢丢。

async await 语法糖

直接上代码

代码语言:javascript复制
async function run() {
    let info = await promise1;
    await promise2(info);
    await promise3(2000);
    console.log('ok');
}

async函数是在ES2017 标准中引入的,使我们异步的代码更加优雅了。这里使用async await 代替了.then()方法。

  • async必须在函数声明前
  • await 接一个 promise,那么后面的代码就会等待,等promise resolve了才会执行。

0 人点赞