关于node.js:深入理解nodejs中的异步编程

42次阅读

共计 4908 个字符,预计需要花费 13 分钟才能阅读完成。

简介

因为 javascript 默认状况下是单线程的,这意味着代码不能创立新的线程来并行执行。然而对于最开始在浏览器中运行的 javascript 来说,单线程的同步执行环境显然无奈满足页面点击,鼠标挪动这些响应用户的性能。于是浏览器实现了一组 API,能够让 javascript 以回调的形式来异步响应页面的申请事件。

更进一步,nodejs 引入了非阻塞的 I/O,从而将异步的概念扩大到了文件拜访、网络调用等。

明天,咱们将会深刻的探讨一下各种异步编程的优缺点和发展趋势。

同步异步和阻塞非阻塞

在探讨 nodejs 的异步编程之前,让咱们来探讨一个比拟容易混同的概念,那就是同步,异步,阻塞和非阻塞。

所谓阻塞和非阻塞是指过程或者线程在进行操作或者数据读写的时候,是否须要期待,在期待的过程中是否进行其余的操作。

如果须要期待,并且期待过程中线程或过程无奈进行其余操作,只能傻傻的期待,那么咱们就说这个操作是阻塞的。

反之,如果过程或者线程在进行操作或者数据读写的过程中,还能够进行其余的操作,那么咱们就说这个操作是非阻塞的。

同步和异步,是指拜访数据的形式,同步是指须要被动读取数据,这个读取过程可能是阻塞或者是非阻塞的。而异步是指并不需要被动去读取数据,是被动的告诉。

很显著,javascript 中的回调是一个被动的告诉,咱们能够称之为异步调用。

javascript 中的回调

javascript 中的回调是异步编程的一个十分典型的例子:

document.getElementById('button').addEventListener('click', () => {console.log('button clicked!');
})

下面的代码中,咱们为 button 增加了一个 click 事件监听器,如果监听到了 click 事件,则会登程回调函数,输入相应的信息。

回调函数就是一个一般的函数,只不过它被作为参数传递给了 addEventListener,并且只有事件触发的时候才会被调用。

上篇文章咱们讲到的 setTimeout 和 setInterval 实际上都是异步的回调函数。

回调函数的错误处理

在 nodejs 中怎么解决回调的错误信息呢?nodejs 采纳了一个十分奇妙的方法,在 nodejs 中,任何回调函数中的第一个参数为谬误对象,咱们能够通过判断这个谬误对象的存在与否,来进行相应的错误处理。

fs.readFile('/ 文件.json', (err, data) => {if (err !== null) {
    // 处理错误
    console.log(err)
    return
  }

  // 没有谬误,则解决数据。console.log(data)
})

回调天堂

javascript 的回调尽管十分的优良,它无效的解决了同步解决的问题。然而遗憾的是,如果咱们须要依赖回调函数的返回值来进行下一步的操作的时候,就会陷入这个回调天堂。

叫回调天堂有点夸大了,然而也是从一方面反映了回调函数所存在的问题。

fs.readFile('/a.json', (err, data) => {if (err !== null) {fs.readFile('/b.json',(err,data) =>{//callback inside callback})
  }
})

怎么解决呢?

别怕 ES6 引入了 Promise,ES2017 引入了 Async/Await 都能够解决这个问题。

ES6 中的 Promise

什么是 Promise

Promise 是异步编程的一种解决方案,比传统的解决方案“回调函数和事件”更正当和更弱小。

所谓 Promise,简略说就是一个容器,外面保留着某个将来才会完结的事件(通常是一个异步操作)的后果。

从语法上说,Promise 是一个对象,从它能够获取异步操作的音讯。

Promise 的特点

Promise 有两个特点:

  1. 对象的状态不受外界影响。

Promise 对象代表一个异步操作,有三种状态:Pending(进行中)、Resolved(已实现,又称 Fulfilled)和 Rejected(已失败)。

只有异步操作的后果,能够决定以后是哪一种状态,任何其余操作都无奈扭转这个状态。

  1. 一旦状态扭转,就不会再变,任何时候都能够失去这个后果。

Promise 对象的状态扭转,只有两种可能:从 Pending 变为 Resolved 和从 Pending 变为 Rejected。

这与事件(Event)齐全不同,事件的特点是,如果你错过了它,再去监听,是得不到后果的。

Promise 的长处

Promise 将异步操作以同步操作的流程表达出来,防止了层层嵌套的回调函数。

Promise 对象提供对立的接口,使得管制异步操作更加容易。

Promise 的毛病

  1. 无奈勾销 Promise,一旦新建它就会立刻执行,无奈中途勾销。
  2. 如果不设置回调函数,Promise 外部抛出的谬误,不会反馈到内部。
  3. 当处于 Pending 状态时,无奈得悉目前停顿到哪一个阶段(刚刚开始还是行将实现)。

Promise 的用法

Promise 对象是一个构造函数,用来生成 Promise 实例:

var promise = new Promise(function(resolve, reject) { 
// ... some code 
if (/* 异步操作胜利 */){resolve(value); 
} else {reject(error); } 
}
);

promise 能够接 then 操作,then 操作能够接两个 function 参数,第一个 function 的参数就是构建 Promise 的时候 resolve 的 value,第二个 function 的参数就是构建 Promise 的 reject 的 error。

promise.then(function(value) {// success}, function(error) {// failure}
);

咱们看一个具体的例子:

function timeout(ms){return new Promise(((resolve, reject) => {setTimeout(resolve,ms,'done');
    }))
}

timeout(100).then(value => console.log(value));

Promise 中调用了一个 setTimeout 办法,并会定时触发 resolve 办法,并传入参数 done。

最初程序输入 done。

Promise 的执行程序

Promise 一经创立就会立马执行。然而 Promise.then 中的办法,则会等到一个调用周期过后再次调用,咱们看上面的例子:

let promise = new Promise(((resolve, reject) => {console.log('Step1');
    resolve();}));

promise.then(() => {console.log('Step3');
});

console.log('Step2');

输入:Step1
Step2
Step3

async 和 await

Promise 当然很好,咱们将回调天堂转换成了链式调用。咱们用 then 来将多个 Promise 连接起来,前一个 promise resolve 的后果是下一个 promise 中 then 的参数。

链式调用有什么毛病呢?

比方咱们从一个 promise 中,resolve 了一个值,咱们须要依据这个值来进行一些业务逻辑的解决。

如果这个业务逻辑很长,咱们就须要在下一个 then 中写很长的业务逻辑代码。这样让咱们的代码看起来十分的冗余。

那么有没有什么方法能够间接返回 promise 中 resolve 的后果呢?

答案就是 await。

当 promise 后面加上 await 的时候,调用的代码就会进行直到 promise 被解决或被回绝。

留神 await 肯定要放在 async 函数中,咱们来看一个 async 和 await 的例子:

const logAsync = () => {
  return new Promise(resolve => {setTimeout(() => resolve('小马哥'), 5000)
  })
}

下面咱们定义了一个 logAsync 函数,该函数返回一个 Promise,因为该 Promise 外部应用了 setTimeout 来 resolve,所以咱们能够将其看成是异步的。

要是应用 await 失去 resolve 的值,咱们须要将其放在一个 async 的函数中:

const doSomething = async () => {const resolveValue = await logAsync();
  console.log(resolveValue);
}

async 的执行程序

await 实际上是去期待 promise 的 resolve 后果咱们把下面的例子联合起来:

const logAsync = () => {
    return new Promise(resolve => {setTimeout(() => resolve('小马哥'), 1000)
    })
}

const doSomething = async () => {const resolveValue = await logAsync();
    console.log(resolveValue);
}

console.log('before')
doSomething();
console.log('after')

下面的例子输入:

before
after
小马哥 

能够看到,aysnc 是异步执行的,并且它的程序是在以后这个周期之后。

async 的特点

async 会让所有前面接的函数都变成 Promise,即便前面的函数没有显示的返回 Promise。

const asyncReturn = async () => {return 'async return'}

asyncReturn().then(console.log)

因为只有 Promise 能力在前面接 then,咱们能够看出 async 将一个一般的函数封装成了一个 Promise:

const asyncReturn = async () => {return Promise.resolve('async return')
}

asyncReturn().then(console.log)

总结

promise 防止了回调天堂,它将 callback inside callback 改写成了 then 的链式调用模式。

然而链式调用并不不便浏览和调试。于是呈现了 async 和 await。

async 和 await 将链式调用改成了相似程序程序执行的语法,从而更加不便了解和调试。

咱们来看一个比照,先看下应用 Promise 的状况:

const getUserInfo = () => {return fetch('/users.json') // 获取用户列表
    .then(response => response.json()) // 解析 JSON
    .then(users => users[0]) // 抉择第一个用户
    .then(user => fetch(`/users/${user.name}`)) // 获取用户数据
    .then(userResponse => userResponse.json()) // 解析 JSON
}

getUserInfo()

将其改写成 async 和 await:

const getUserInfo = async () => {const response = await fetch('/users.json') // 获取用户列表
  const users = await response.json() // 解析 JSON
  const user = users[0] // 抉择第一个用户
  const userResponse = await fetch(`/users/${user.name}`) // 获取用户数据
  const userData = await userResponse.json() // 解析 JSON
  return userData
}

getUserInfo()

能够看到业务逻辑变得更加清晰。同时,咱们获取到了很多两头值,这样也不便咱们进行调试。

本文作者:flydean 程序那些事

本文链接:http://www.flydean.com/nodejs-async/

本文起源:flydean 的博客

欢送关注我的公众号:「程序那些事」最艰深的解读,最粗浅的干货,最简洁的教程,泛滥你不晓得的小技巧等你来发现!

正文完
 0