如何深入理解await-to-js源码,改写异步任务用法示例为长尾词?

2026-03-31 17:441阅读0评论SEO问题
  • 内容介绍
  • 文章标签
  • 相关推荐

本文共计3147个文字,预计阅读时间需要13分钟。

如何深入理解await-to-js源码,改写异步任务用法示例为长尾词?

目录- 如何处理异步任务?- 回调函数- Promise- async- Generator- 什么是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函数进行处理:

        如何深入理解await-to-js源码,改写异步任务用法示例为长尾词?

        // 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]; } });

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

        本文共计3147个文字,预计阅读时间需要13分钟。

        如何深入理解await-to-js源码,改写异步任务用法示例为长尾词?

        目录- 如何处理异步任务?- 回调函数- Promise- async- Generator- 什么是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函数进行处理:

              如何深入理解await-to-js源码,改写异步任务用法示例为长尾词?

              // 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]; } });

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