异步编程01:Promise的横空出世

阅读 2.3k
标签: JavaScript

本系列聊聊异步编程,虽然有很多文章已经说过了,但我决定再写一点,主要是因为其重要性,主要分为两部分:

  • 第一部分,聊Promise
  • 第二部分,聊asyncawait

异步编程在代码中占有非常重要的地位,对于前端而言,常见的事件模型ajax请求等都是异步的。

如果你使用Node.js进行服务端编程的话,数据库请求文件操作等也都是异步的。

所以,在所有的ES6新特性中,Promise是一个需要优先学习的特性,因为知识的投资收益比很高。

回调地狱的终结者

首先,说说为什么要引入Promise。

在早期的Node.js中,使用回调方式来进行异步编程,我们来看一段代码:

readFile('example.json',  (data) => {console.log(data)})

一层回调,非常清晰,OK,我们再看一段代码:

readFile('example.json', (data) => {
  writeFile('example_copy.json', data, (msg) => {
        console.log(`write finished: ${msg}`)
    })
})

已经是两层回调了,先读取再写入。因为示例代码经过了简化,所以两层回调看上去好像也没什么问题。但请注意,在实际的编程中,每一层回调都可能会处理一些业务逻辑,所以每一层的代码远比上面的示例代码要多得多,也复杂得多。况且,常常会超过两层回调。

在多层回调的情况下,去维护每一层的代码就成了一个头疼的问题,这就是众所周知的回调地狱回调地狱之所以可怕,就是因为其一层层深入的嵌套。那么有没有一种方法,能够让嵌套最多不超过两层呢?

标准组在ES2015中引入了Promise,Promise横空出世!其强大之处就是它的then链可以约束嵌套层数。

对于上面的示例,如果用Promise来处理:

readFileTask
  .then(readData => {
    console.log(`read finished: ${readData}`)
    return writeFile('example_copy.json', readData) // 继续写入
  })
  .then(msg => {
    console.log(`write finished: ${msg}`)
  })
  .catch(err => console.log(err))

用Promise的方式,在读取任务完成后,会将第二个异步任务(writeFile方法)作为一个Promise对象返回,再通过链式调用继续在下一个then方法中获取写入任务的执行结果。

不管你有多少层的回调,都可以通过then链处理,3层回调就是3个then,4层回调就是4个then,回调层数再多,Promise也能将你约束在两层之中!这样,就很好地解决了层层深入的嵌套问题。

说完为什么需要Promise之后,我们再来看看Promise是什么。

真实身份:Placeholder

先来看看标准怎么说的,毕竟标准最大嘛

A Promise is an object that is used as a placeholder for the eventual results of a deferred (and possibly asynchronous) computation.

是一个对象,专门“占坑”的对象。对于异步任务,我们常常不知道什么时候会完成,假如能有一个占位符(placeholder)表示异步任务的执行情况,那么就可以很方便地去跟踪这些异步任务,Promise实例就是用来干这个的。如果你理解了这个placeholder的含义,也就差不多理解了Promise。

既然命名为Promise,那是不是它就是一个构造函数呢?我们先打印出来瞧瞧:


原来如此,它确实是一个构造函数,可以通过它来创建一个实例对象,这个对象专门“占坑”。

其自身有如下常见的静态方法:

  • all
  • race
  • reject
  • resolve

其原型上还定义了如下常见方法:

  • then
  • catch
  • finally

怎么使用Promise

最后,来聊聊怎么使用,它的语法很简单:
new Promise(executor)

executor是一个带双参的函数,参数为resolvereject。Promise会立即执行这个executor,executor通常会包含一些异步运算,一旦运算成功完成,则resolve掉这个promise,如果出错则reject掉。

我这里精心准备了两个例子,来让大家熟悉下。

异步例子

我们先看异步的情况:

function readFile(filePath) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log('readFile finish...')
      resolve(`some data from ${filePath}`)
    }, 3000)
  })
}

const placeholder = readFile('example.json')

placeholder
  .then(d => {
    console.log(d)
  })
  .catch(e => {
    console.log(e)
  })

上面代码中,定义了一个readFile函数,其会返回一个Promise实例。当调用这个readFile函数时,会立刻返回创建的Promise实例(此时是pending状态),并将其赋值给placeholder。

请注意:这里的立刻的意义,虽然executor中的异步任务需要执行3s,但是这个promise作为占位符,是会立刻生成的,只是此时的状态为pengding,3s后,异步任务执行完成,其状态变成fulfilled,如果异步任务执行失败(比如发生错误等),则其状态会变成rejected

注意:executor中并不一定非得是异步代码,也可以是同步代码。

同步例子

我们再看一个有趣的同步例子:

function readFile(filePath) {
  return new Promise((resolve, reject) => {
    console.log(100)
    resolve(`some data from ${filePath}`)
    console.log(200)
  })
}

console.log('start...')
const placeholder = readFile('example.json')
console.log(placeholder)

placeholder
  .then(d => {
    console.log(d)
  })
  .catch(e => {
    console.log(e)
  })

console.log('end...')

最终结果为:

start...
100
200
Promise { 'some data from example.json' }
end...
some data from example.json

是否和你想的一样呢?

小结

当然,Promise更多的是用在处理异步代码,上面列举的同步例子是为了让大家更好去理解,如果现在你还是不太理解Promise,没关系,我额外准备了一篇《异步编程02:Promise.then()》,应该会对你理解Promise大有帮助。

参考

最后编辑于: 2022-06-28

评论(0条)

(必填)
复制成功