await-to-js源码深入理解处理异步任务用法示例

目录
  • 如何处理异步任务?
    • 回调函数
    • Promise
  • async
    • Generator
  • 什么是await-to-js?
  • 源码解析
  • 最后 

如何处理异步任务?

我们先从一个老生常谈的问题开始。

回调函数

由于javascript是一门单线程的语言,所以我们早期来处理异步场景的时候,大部分是通过回调函数来进行处理的。

var fn = function(callback){
    setTimeout(function(){
        callback()
    },1000)
}
fn(function(){console.log('hello, pino')})

例如上面这个例子,fn函数是一个异步函数,里面执行的setTimeout将会在1s之后调用传入的callback函数,打印出hello,pino这个结果。

但是当我们有多个异步操作的时候,就需要有多个异步函数进行嵌套,代码将会变得更加臃肿和难以维护。

setTimeout(function(){
    console.log('执行了')
    setTimeout(function(){
        console.log('再次执行了')
        //.....
    },2000)
},1000)

同样的,还有一个例子: 假设我们有fn1,fn2,fn3三个异步函数:

let fn1 = function(){
    setTimeout(function(){
        console.log('pino')
    },1000)
}
let fn2 = function(){
    setTimeout(function(){
        console.log('爱吃')
    },3000)
}
let fn3 = function(){
    setTimeout(function(){
        console.log('瓜')
    },2000)
}

我们想顺序对三个函数的结果进行顺序打印,那么使用传统的回调函数来实现的话,我们可以这样写:

var makefn = function(text,callback,timer){
    setTimeout(function(){
        console.log(text)
        callback()
    },timer)
}
makefn('pino',function(){
    makefn('爱吃',function(){
        makefn('瓜',function(){
            console.log('结束了~')
        },2000)
    },3000)
},1000)

可以看到当回调任务过多的时候,我们的代码将会变的非常臃肿,尤其是多个异步函数之间层层嵌套,这就形成了回调地狱。

使用回调函数的方式来处理异步任务,当回调函数过多时,对开发者的心智负担是非常重的。

Promise

promise对象的出现其实是对js处理异步任务迈出的一大步,它提供了非常多的针对异步的处理方法,错误捕获链式调用...

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

Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolvereject

resolve函数: 将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;

reject函数: 将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。

const person = new Promise((resolve,reject) => {
    let num = 6;
    if(num>5){
        resolve()
    }else{
        reject()
    }
})

Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数。

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

then方法可以接受两个回调函数作为参数。第一个回调函数是Promise对象的状态变为resolved时调用,第二个回调函数是Promise对象的状态变为rejected时调用。其中,第二个函数是可选的,这两个函数都接受Promise对象传出的值作为参数。

例如我们将上面的顺序打印三个异步函数进行改造:

makefn('pino',function(){
    makefn('爱吃',function(){
        makefn('瓜',function(){
            console.log('结束了~')
        },2000)
    },3000)
},1000)
//改造后
fn('pino',1000).then(function(){
    return fn('爱吃',3000)
})
.then(function(){
    return fn('瓜',2000)
})
.then(function(){
    console.log('结束了~')
})

可以看到改造完成后的代码变得非常具有可读性和条理性。 由于本文的主角不是Promise对象,所以想要深入了解请移步:es6.ruanyifeng.com/#docs/promi…

async

ES2017 标准引入了 async 函数,使得异步操作变得更加方便。

async函数返回一个 Promise 对象,可以使用then方法添加回调函数。当函数执行的时候,一旦遇到await就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。

// 函数前面加入async关键字
async function getAllData(name) {
  // 遇到await会暂停,并返回值
  const data = await getData(name);
  const options = await getSelect(name);
  return options;
}
getAllData('pino').then(function (result) {
  console.log(result);
});

下面继续使用async的方式来改造一下文章开头的例子:

async function makeFn() {
  let fn1 = await fn1()
  let fn2 = await fn2()
  let fn3 = await fn3()
}

async函数的出现几乎将异步函数完全变为了同步的写法,使异步任务更容易维护。

Generator

形式上, Generator 函数是一个普通函数,但是有两个特征。

一是,function关键字与函数名之间有一个星号;

二是,函数体内部使用yield表达式,定义不同的内部状态(yield在英语里的意思就是“产出”)。

function* helloWorldGenerator() {
  yield 'hello';
  yield 'world';
  return 'ending';
}
var p1 = helloWorldGenerator();

上面代码定义了一个 Generator 函数helloWorldGenerator,它内部有两个yield表达式(hello和world

即该函数有三个状态:hello,worldreturn 语句(结束执行)。

然后, Generator 函数的调用方法与普通函数一样,也是在函数名后面加上一对圆括号。

不同的是,调用 Generator 函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象,也就是上一章介绍的遍历器对象(Iterator Object)。 下一步,必须调用遍历器对象的next方法,使得指针移向下一个状态。

也就是说,每次调用next方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield表达式(或return语句)为止。

换言之, Generator 函数是分段执行的,yield表达式是暂停执行的标记,而next方法可以恢复执行。

p1.next()
// { value: 'hello', done: false }
p1.next()
// { value: 'world', done: false }
p1.next()
// { value: 'ending', done: true }
p1.next()
// { value: undefined, done: true }

上面代码一共调用了四次next方法。

Generator 函数也可以进行异步任务的处理,上面的async函数就是Generator 函数的语法糖,而两者之间最大的区别就是async函数内置了自执行器,也就是说无需手动调用next()方法,async函数就会帮我们继续向下执行,而Generator 函数不会自动调用next()方法,只能进行手动调用,下面实现一个简易执行器:

// 接受一个Generator函数
function run(gen){
  var g = gen();
  function next(data){
    var result = g.next(data);
    if (result.done) return result.value;
    // 只要返回的dong不为true,没有执行完毕,就继续调用next函数,继续执行
    result.value.then(function(data){
      next(data);
    });
  }
  next();
}
run(gen);

使用Generator 函数来该写一下之前的案例,其实只需要将await更换为yield

function* makeFn() {
  let fn1 = yield fn1()
  let fn2 = yield fn2()
  let fn3 = yield fn3()
}

本文只是简略的讲解了Generator 函数和async函数,如果像深入学习,请移步:

es6.ruanyifeng.com/#docs/async

es6.ruanyifeng.com/#docs/gener…

什么是await-to-js?

说了这么多,今天的主角await-to-js到底是干啥的?解决了什么问题

先来看一下作者的定义:

Async await wrapper for easy error handling without try-catch。

异步等待封装器,便于错误处理,不需要try-catch

先来看一下如何使用:

安装

npm i await-to-js --save

对比一下使用await-to-js后,我们在代码中处理错误捕获有什么不同,这里使用async函数进行处理:

// async的处理方式
function async getData() {
    try {
      const data1 = await fn1()
    } catch(error) {
      return new Error(error)
    }
    try {
      const data2 = await fn2()
    } catch(error) {
      return new Error(error)
    }
    try {
      const data3 = await fn3()
    } catch(error) {
      return new Error(error)
    }
}
// 使用await-to-js后
import to from './to.js';
function async getData() {
   const [err, data1]  = await to(promise)
   if(err) throw new (error);
   const [err, data2]  = await to(promise)
   if(err) throw new (error);
   const [err, data3]  = await to(promise)
   if(err) throw new (error);
}

可以看到,使用await-to-js后我们的代码变得精简了许多,在使用async函数时,需要手动使用try...catch来进行错误捕获,而await-to-js直接就可以将错误返回给用户。

所以根据上面的例子,可以得出结论,await-to-js的作用就是封装了错误捕获的处理函数,使异步的操作更加的方便。

那么await-to-js是如何实现的呢?

源码解析

其实await-to-js的源码非常短,只有15行,可以直接看一下源码中是如何实现的(为了查看源码更加的直观,下面的源码已经去除了typescript语法):

export function to(
  promise,
  errorExt
){
  return promise
    .then((data) => [null, data])
    .catch((err) => {
      if (errorExt) {
        const parsedError = Object.assign({}, err, errorExt);
        return [parsedError, undefined];
      }
      return [err, undefined];
    });
}

可以看到await-to-js中直接返回了to函数,他接受两个参数,promiseerrorExt,其中promise参数接受一个Promis对象,而errorExt参数是可选的,先来看一下如果不传入errorExt参数是什么样子的:

export function to(promise, errorExt){
  // 使用then和catch来执行和捕获错误
  return promise
    .then((data) => [null, data])
    .catch((err) => {
      return [err, undefined];
    });
}

to函数直接返回了传入的Promise对象,并定义了then函数和catch函数,无论成功还是失败都返回一个数组,数组的第一项是错误结果,如果执行成功则返回null,执行失败则返回错误信息,数组的第二项为执行结果,执行成功则返回响应成功的结果,如果执行失败则返回undefined,这也是非常符合预期的。

那么第二个参数是干什么的呢?第二个参数errorExt是可选的,他接收一个对象,主要用于接收用户自定义的错误信息,然后使用Object.assign将自定义信息与错误信息合并到一个对象,返回给用户。

.catch((err) => {
  if (errorExt) {
    // 合并错误对象:默认错误信息+用户自定义错误信息
    const parsedError = Object.assign({}, err, errorExt);
    // 返回错误结果
    return [parsedError, undefined];
  }
});

刚开始看源码的时候各种不适应,但是只要沉下心去一步一步的调试,结合测试用例,有些东西真的没有想象中那么难,主要还是重在行动,想到了一个念头和想法就赶紧去做,拒绝拖沓,只有真正的行动去学习,去获取,去感知,才能真正的进步!

最后 

未来可能会更新实现mini-vue3javascript基础知识系列,希望能一直坚持下去,期待多多点赞

(0)

相关推荐

  • useEffect支持async及await使用方式

    目录 引言 背景 React 为什么要这么做? useEffect 怎么支持 async...await... 自定义 hooks 还可以支持 useEffect 的清除机制么? 总结与思考 引言 本文是深入浅出 ahooks 源码系列文章的第六篇,这个系列的目标主要有以下几点: 加深对 React hooks 的理解. 学习如何抽象自定义 hooks.构建属于自己的 React hooks 工具库. 培养阅读学习源码的习惯,工具库是一个对源码阅读不错的选择. 注:本系列对 ahooks 的源码

  • vue中异步函数async和await的用法说明

    目录 异步函数async和await用法 async/await为什么叫异步 外异内同 异步函数async和await用法 先说一下async的用法,它作为一个关键字放到函数前面,用于表示函数是一个异步函数,因为async就是异步的意思, 异步函数也就意味着该函数的执行不会阻塞后面代码的执行. 写一个async函数 async function timeout() { return 'hello world'; } 语法很简单,就是在函数前面加上async关键字,来表示它是异步的,那怎么调用呢?

  • .NET实现异步编程async和await

    await和async是.NET Framework4.5框架.C#5.0语法里面出现的,await和async是语法糖. 注意: 1.async出现在方法的声明里面,任何一个方法都可以增加async. 2.await放在Task前面,async和await是成对出现的,只有async是没有意义的,只有await是报错的. 只有async是没有意义的. 只有await是报错的. 3.await 只能放在task前面,不推荐void返回值,使用Task来代替.Task和Task<T>能够使用aw

  • JavaScript引擎实现async/await的方法实例

    目录 前言 生成器 VS 协程 async/await async await 小结 总结 前言 我们都知道Promise 能很好地解决回调地狱的问题,但是这种方式充满了 Promise 的 then() 方法,如果处理流程比较复杂的话,那么整段代码将充斥着 then,语义化不明显,代码不能很好地表示执行流程,使用 promise.then 也是相当复杂,虽然整个请求流程已经线性化了,但是代码里面包含了大量的 then 函数,使得代码依然不是太容易阅读.基于这个原因,ES7 引入了 async/

  • 使用async await处理错误方法示例

    目录 Promise封装请求 async/await await-to-js 源码很简单 使用很简单 Promise封装请求 大家平时如果使用Promise封装请求,那么当你使用这个请求函数的时候是这样的: // 封装请求函数 const request = (url, params) => { return new Promise((resolve, reject) => { // ...do something }) } // 使用时 const handleLogin = () =>

  • await-to-js源码深入理解处理异步任务用法示例

    目录 如何处理异步任务? 回调函数 Promise async Generator 什么是await-to-js? 源码解析 最后  如何处理异步任务? 我们先从一个老生常谈的问题开始. 回调函数 由于javascript是一门单线程的语言,所以我们早期来处理异步场景的时候,大部分是通过回调函数来进行处理的. var fn = function(callback){ setTimeout(function(){ callback() },1000) } fn(function(){console

  • 深入理解Vue.js源码之事件机制

    写在前面 因为对Vue.js很感兴趣,而且平时工作的技术栈也是Vue.js,这几个月花了些时间研究学习了一下Vue.js源码,并做了总结与输出. 文章的原地址:https://github.com/answershuto/learnVue. 在学习过程中,为Vue加上了中文的注释https://github.com/answershuto/learnVue/tree/master/vue-src,希望可以对其他想学习Vue源码的小伙伴有所帮助. 可能会有理解存在偏差的地方,欢迎提issue指出

  • 详解从Vue.js源码看异步更新DOM策略及nextTick

    写在前面 因为对Vue.js很感兴趣,而且平时工作的技术栈也是Vue.js,这几个月花了些时间研究学习了一下Vue.js源码,并做了总结与输出. 文章的原地址:https://github.com/answershuto/learnVue. 在学习过程中,为Vue加上了中文的注释https://github.com/answershuto/learnVue/tree/master/vue-src,希望可以对其他想学习Vue源码的小伙伴有所帮助. 可能会有理解存在偏差的地方,欢迎提issue指出,

  • Three.js源码阅读笔记(Object3D类)

    这是Three.js源码阅读笔记的第二篇,直接开始. Core::Object3D Object3D似乎是Three.js框架中最重要的类,相当一部分其他的类都是继承自Object3D类,比如场景类.几何形体类.相机类.光照类等等:他们都是3D空间中的对象,所以称为Object3D类.Object3D构造函数如下: 复制代码 代码如下: THREE.Object3D = function () { THREE.Object3DLibrary.push( this ); this.id = THR

  • Three.js源码阅读笔记(光照部分)

    天气越来越冷了,人也越来越懒怠,越来越像呆在温暖的寝室里看小说或者打游戏,也好久没看Three.js源码了.今天天气不错,接着看! 这次从光照部分看起:光照模型,从光线本身角度来看包括环境光.平行光.点光源,从物体表面材质角度看又包括漫反射和镜面反射. Lights:Light 复制代码 代码如下: THREE.Light = function ( hex ) { THREE.Object3D.call( this ); this.color = new THREE.Color( hex );

  • Three.js源码阅读笔记(物体是如何组织的)

    这是Three.js源码阅读笔记第三篇.之前两篇主要是关于核心对象的,这些核心对象主要围绕着矢量vector3对象和矩阵matrix4对象展开的,关注的是空间中的单个顶点的位置和变化.这一篇将主要讨论Three.js中的物体是如何组织的:即如何将顶点.表面.材质组合成为一个具体的对象. Object::Mesh 该构造函数构造了一个空间中的物体.之所以叫"网格"是因为,实际上具有体积的物体基本都是建模成为"网格"的. 复制代码 代码如下: THREE.Mesh =

  • Vue.js源码分析之自定义指令详解

    前言 除了核心功能默认内置的指令 (v-model 和 v-show),Vue 也允许注册自定义指令. 官网介绍的比较抽象,显得很高大上,我个人对自定义指令的理解是:当自定义指令作用在一些DOM元素或组件上时,该元素在初次渲染.插入到父节点.更新.解绑时可以执行一些特定的操作(钩子函数() 自定义指令有两种注册方式,一种是全局注册,使用Vue.directive(指令名,配置参数)注册,注册之后所有的Vue实例都可以使用,另一种是局部注册,在创建Vue实例时通过directives属性创建局部指

  • jQuery实例—选项卡的简单实现(js源码和jQuery)

    分别利用javascript的源码和jQuery来实现一个简单的选项卡,对比各自的步骤. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> &l

  • Android View事件分发和消费源码简单理解

    Android View事件分发和消费源码简单理解 前言: 开发过程中觉得View事件这块是特别烧脑的,看了好久,才自认为看明白.中间上网查了下singwhatiwanna粉丝的读书笔记,有种茅塞顿开的感觉. 很重要的学习方法:化繁为简,只抓重点. 源码一坨,不要指望每一行代码都看懂.首先是没必要,其次大量非关键代码会让你模糊真正重要的部分. 以下也只是学姐的学习成果,各位同学要想理解深刻,还需要自己亲自去看源码. 2.源码分析 由于源码实在太长,而且也不容易看懂,学姐这里就不贴出来了,因为没必

  • Three.js源码阅读笔记(基础的核心Core对象)

    Three.js是一个比较伟大的webgl开源库,它简化了浏览器3D编程,使得使用JavaScript在浏览器中创建复杂的场景变得容易很多.Github上众多webgl demo令我兴奋不已,跃跃欲试.由于这个库还处在开发阶段,因此资料非常匮乏,爱好者大部分时间不得不通过阅读该库的源码进行学习,我现在也准备这样做. 这是第一篇笔记,先从最基础的核心(Core)对象开始. Core::Vector2 该构造函数用来创建一个表示二维向量的对象 复制代码 代码如下: THREE.Vector2 = f

随机推荐