最新文章专题视频专题问答1问答10问答100问答1000问答2000关键字专题1关键字专题50关键字专题500关键字专题1500TAG最新视频文章推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37视频文章20视频文章30视频文章40视频文章50视频文章60 视频文章70视频文章80视频文章90视频文章100视频文章120视频文章140 视频2关键字专题关键字专题tag2tag3文章专题文章专题2文章索引1文章索引2文章索引3文章索引4文章索引5123456789101112131415文章专题3
当前位置: 首页 - 科技 - 知识百科 - 正文

新手如何快速理解js异步编程

来源:动视网 责编:小采 时间:2020-11-27 21:54:41
文档

新手如何快速理解js异步编程

新手如何快速理解js异步编程:前言 异步编程从早期的 callback、事件发布\订阅模式到 ES6 的 Promise、Generator 在到 ES2017 中 async,看似风格迥异,但是还是有一条暗线将它们串联在一起的,就是希望将异步编程的代码表达尽量地贴合自然语言的线性思维。 以这条暗线将上述几种解决方
推荐度:
导读新手如何快速理解js异步编程:前言 异步编程从早期的 callback、事件发布\订阅模式到 ES6 的 Promise、Generator 在到 ES2017 中 async,看似风格迥异,但是还是有一条暗线将它们串联在一起的,就是希望将异步编程的代码表达尽量地贴合自然语言的线性思维。 以这条暗线将上述几种解决方


前言

异步编程从早期的 callback、事件发布\订阅模式到 ES6 的 Promise、Generator 在到 ES2017 中 async,看似风格迥异,但是还是有一条暗线将它们串联在一起的,就是希望将异步编程的代码表达尽量地贴合自然语言的线性思维。

以这条暗线将上述几种解决方案连在一起,就可以更好地理解异步编程的原理、魅力。
├── 事件发布\订阅模式 <= Callback
├── Promise <= 事件发布\订阅模式
├── Async、Await <= Promise、Generator

事件发布\订阅模式 <= Callback

这个模式本质上就是回调函数的事件化。它本身并无同步、异步调用的问题,我们只是使用它来实现事件与回调函数之间的关联。比较典型的有 NodeJS 的 events 模块

const { EventEmitter } = require('events')
const eventEmitter = new EventEmitter()
// 订阅
eventEmitter.on("event", function(msg) {
console.log("event", msg)
})
// 发布
eventEmitter.emit("event", "Hello world")

那么这种模式是如何与 Callback 关联的呢?我们可以利用 Javascript 简单实现 EventEmitter,答案就显而易见了。

class usrEventEmitter {
constructor () {
this.listeners = {}
}
// 订阅,callback 为每个 event 的侦听器
on(eventName, callback) {
if (!this.listeners[eventName]) this.listeners[eventName] = []
this.listeners[eventName].push(callback)
}
// 发布
emit(eventName, params) {
this.listeners[eventName].forEach(callback => {
callback(params)
})
}
// 注销
off(eventName, callback) {
const rest = this.listeners[eventName].fitler(elem => elem !== callback)
this.listeners[eventName] = rest
}
// 订阅一次
once(eventName, callback) { 
const handler = function() {
callback()
this.off(eventName, handler)
}
this.on(eventName, handler)
}
}

上述实现忽略了很多细节,例如异常处理、多参数传递等。只是为了展示事件订阅\发布模式。

很明显的看出,我们使用这种设计模式对异步编程做了逻辑上的分离,将其语义化为

// 一些事件可能会被触发
eventEmitter.on
// 当它发生的时候,要这样处理
eventEmitter.emit

也就是说,我们将最初的 Callback 变成了事件,从而优雅地解决异步编程。

Promise <= 事件发布\订阅模式

使用事件发布\订阅模式时,需要我们事先严谨地设置目标,也就是上面所说的,必须要缜密地设定好有哪些事件会发生。这与我们语言的线性思维很违和。那么有没有一种方式可以解决这个问题,社区产出了 Promise。

const promise = new Promise(function(resolve, reject) {
try {
setTimeout(() => {
resolve('hello world')
}, 500)
} catch (error) {
reject(error)
}
})
// 语义就变为先发生一些异步行为,then 我们应该这么处理 promise.then(msg => console.log(msg)).catch(error => console.log('err', error))

那么这种 Promise 与事件发布\订阅模式有什么联系呢?我们可以利用 EventEmitter 来实现 Promise,这样可能会对你有所启发。

我们可以将 Promise 视为一个 EventEmitter,它包含了 { state: 'pending' } 来描述当前的状态,同时侦听它的变化

  • 当成功时 { state: 'fulfilled' },要做些什么 on('resolve', callback);
  • 当失败时 { state: 'rejected' },要做些什么 on('reject', callback)。
  • 具体实现如下

    const { EventEmitter } = require('events')
    class usrPromise extends EventEmitter {
    // 构造时候执行
    constructor(executor) {
    super()
    // 发布
    const resolve = (value) => this.emit('resolve', value)
    const reject = (reason) => this.emit('reject', reason)
    if (executor) {
    // 模拟 event loop,注此处利用 Macrotask 来模拟 Microtask
    setTimeout(() => executor(resolve, reject))
    }
    }
    then(resolveHandler, rejectHandler) {
    const nextPromise = new usrPromise()
    // 订阅 resolve 事件
    if (resolveHandler) {
    const resolve = (data) => {
    const result = resolveHandler(data)
    nextPromise.emit('resolve', result)
    }
    this.on('resolve', resolve)
    }
    // 订阅 reject 事件
    if (rejectHandler) {
    const reject = (data) => {
    const result = rejectHandler(data)
    nextPromise.emit('reject', result)
    }
    this.on('reject', reject)
    } else {
    this.on('reject', (data) => {
    promise.emit('reject', data)
    })
    }
    return nextPromise
    }
    catch(handler) {
    this.on('reject', handler)
    }
    }

    我们使用 then 方法来将预先需要定义的事件侦听器存放起来,同时在 executor 中设定这些事件该在什么时候实行。

    可以看出从事件发布\订阅模式到 Promise,带来了语义上的巨大变革,但是还是需要使用 new Promise 来描述整个状态的转换,那么有没有更好地实现方式呢?

    async、await <= Promise、Generator

    async、await 标准是 ES 2017 引入,提供一种更加简洁的异步解决方案。

    async function say(greeting) {
    return new Promise(function(resolve, then) {
    setTimeout(function() {
    resolve(greeting)
    }, 1500)
    })
    }
    ;(async function() {
    let v1 = await say('Hello')
    console.log(v1)
    let v2 = await say('World')
    console.log(v2)
    })()

    await 可以理解为暂停当前 async function 的执行,等待 Promise 处理完成。若 Promise 正常处理(fulfilled),其回调的resolve函数参数作为 await 表达式的值。

    async、await 的出现,减少了多个 then 的链式调用形式的代码。下面我们结合 Promise 与 Generator 来实现 async、await

    function async(makeGenerator) {
    return function() {
    const generator = makeGenerator.apply(this, arguments)
    function handle({ value, done }) {
    if (done === true) return Promise.resolve(value)
    return Promise.resolve(value).then(
    (res) => {
    return handle(generator.next(res))
    },
    function(err) {
    return handle(generator.throw(err))
    }
    )
    }
    try {
    return handle(generator.next())
    } catch (ex) {
    return Promise.reject(ex)
    }
    }
    }
    async(function*() {
    var v1 = yield say('hello')
    console.log(1, v1)
    var v2 = yield say('world')
    console.log(2, v2)
    })()

    本质上就是利用递归完成 function* () { ... } 的自动执行。相比与 Generator 函数,这种形式无需手动执行,并且具有更好的语义。

    文档

    新手如何快速理解js异步编程

    新手如何快速理解js异步编程:前言 异步编程从早期的 callback、事件发布\订阅模式到 ES6 的 Promise、Generator 在到 ES2017 中 async,看似风格迥异,但是还是有一条暗线将它们串联在一起的,就是希望将异步编程的代码表达尽量地贴合自然语言的线性思维。 以这条暗线将上述几种解决方
    推荐度:
    • 热门焦点

    最新推荐

    猜你喜欢

    热门推荐

    专题
    Top