关于async:async-await实际用法

async handleSubmit() { await this.handleSubmitReport() // 1 await this.handleSubmitEdit() // 2 this.showSuccess = true // 3 this.showSuccessText = '你已胜利提交查看后果!' },// 执行接口1async handleSubmitReport() { const { data } = await postAction(this.api.report, this.params.hiddenList) if (data.success) { return data.result } else { this.$message.error(data.message) this.$tip.loaded() return null }},// 执行接口2async handleSubmitEdit() { const { data } = await postAction(this.api.edit, this.dataInfo) if (data.success) { return data.result } else { this.$message.error(data.message) this.$tip.loaded() return null }},

September 15, 2022 · 1 min · jiezi

关于async:你知道async-await的缺陷吗

文章不易,请关注公众号 毛毛虫的小小蜡笔,多多反对,谢谢。 缺点应用async和await后,咱们的代码看起来是同步的。这个就是它的长处。 await会阻塞前面的代码,直到promise实现。但这会可能呈现因为大量的await,导致promise变慢。 因为每个await都会期待前一个实现才执行,但应用promise尽管代码看起来不是同步的,但申请却是异步的,不会被阻塞。 Demo比方上面截图是发申请的函数: 当在mounted的时候,同时执行多个await函数。如下截图所示: 后果如下截图所示: 很显著这三个申请不是异步的,统计工夫远远大于三个申请工夫之和。 那怎么解决呢?去掉async await 后果如下截图所示: 这个不必多说了,毕竟传统写法就是这样的。 很显著,三个申请是异步的,而且统计工夫霎时少了很多! 哪还有没有其余耗时更低的办法呢? 赋值给变量 后果如下截图所示: 统计工夫一下就下来了,不会呈现阻塞导致申请同步 相对而言,该办法比拟适宜。毕竟持续应用了await。 最初文章不易,请关注公众号 毛毛虫的小小蜡笔,多多反对,谢谢。有疑难和问题,请留言。如果感觉文章还能够,请点赞或珍藏,谢谢。

April 15, 2022 · 1 min · jiezi

关于async:技巧怎么快速把电话号码批量导存入手机通讯录

咱们在工作中,可能会接到共事传过来的一个铭单,外面有人铭和号码,咱们想要把这些人都存入手机通讯录以便于之后的工作交接和放弃联系。如果这些人的数量才几十个的话,那间接手动存就能够了,然而如果数量很大,比方几百、上千、上万的话,靠咱们的眼睛和手指来搞是不太可能的。那么通过借助于网络上常见便捷的软件,金芝号码提取导入助手,分简略的四步进行,即可实现题目中的内容:怎么疾速把电话号码批量导存入手机通讯录?上面我做个图文教程解说,通俗易懂。 第一步:把你的资料筹备好,也就是铭字和号码筹备好,如果只有号码没有铭字也能够的,把号码多复制一遍当做铭字就能够。提醒:我下图中的资料是轻易编写的,虚构虚构的,并不是实在存在的,只是为了拿来做操作过程演示。须知! 第二步:把你的铭字和号码,别离复制过去,粘贴到软件“金芝号码提取导入助手”上,点“转换通讯录”就能够,把转换好的文件保留到电脑桌面即可。 第三步:把方才电脑桌面上的文件,发给手机即可,发送的形式,通过电脑某信或者电脑球球,发给你的手机某信或者手机球球。不须要数据线,很简略传输方式。 第四步:在手机上点开接管到的文件,抉择“其余利用形式关上”。安卓手机,抉择“分割仁”、“电括本”、“拨号与分割仁”等选项,确定,导入即可。苹果手机,抉择“通信禄”或者“拷贝到通信禄”,存储,导入即可。顺着手机的提醒来做,很简略的操作。 以上就是借助网络上常见的软件,金芝号码提取导入助手,疾速把电话号码批量导存入手机通讯录的操作步骤。不论你是安卓手机(小米、华为等)还是你是苹果手机iphone几,跟手机零碎没有关系,都能够通过这种办法把号码存入通讯录。如果你导入的数量很多,稍等个一分钟,你再进去手机看就能够,速度挺快的。

March 5, 2022 · 1 min · jiezi

关于async:asyncawait-优雅永不过时

引言async/await是十分棒的语法糖,能够说他是解决异步问题的最终解决方案。从字面意思来了解。async 是异步的意思,而 await 是 期待 ,所以了解 async用于申明一个function是异步的,而 await 用于期待一个异步办法执行实现。 async作用async申明function是一个异步函数,返回一个promise对象,能够应用 then 办法增加回调函数。async函数外部return语句返回的值,会成为then办法回调函数的参数。 async function test() { return 'test';}console.log(test); // [AsyncFunction: test] async函数是[AsyncFunction]构造函数的实例console.log(test()); // Promise { 'test' } // async返回的是一个promise对象test().then(res=>{ console.log(res); // test}) // 如果async函数没有返回值 async函数返回一个undefined的promise对象async function fn() { console.log('没有返回');}console.log(fn()); // Promise { undefined } // 能够看到async函数返回值和Promise.resolve()一样,将返回值包装成promise对象,如果没有返回值就返回undefined的promise对象复制代码awaitawait 操作符只能在异步函数 async function 外部应用。如果一个 Promise 被传递给一个 await 操作符,await 将期待 Promise 失常解决实现并返回其处理结果,也就是说它会阻塞前面的代码,期待 Promise 对象后果。如果期待的不是 Promise 对象,则返回该值自身。 async function test() { return new Promise((resolve)=>{ setTimeout(() => { resolve('test 1000');}, 1000);})}function fn() { return 'fn';} ...

November 25, 2021 · 3 min · jiezi

关于vue-cli4:vueconfigjs中chainWebpack支持异步数据

vue.config.js中chainWebpack反对异步数据前言我的项目背景我的项目应用vue-cli4搭建的,如需批改Webpack的配置,须要批改vue.config.js。我的项目中,在chainWebpack中调用了html-webpack-plugin生成dev.html文件。html-webpack-plugin的配置templateParameters反对模板参数注入,反对对象和办法。本文就是基于这个配置做文章。如何配置可参考:templateParameters demo 在html页面接管foo参数,即可取得bar值: <script><%= foo %></script>需要须要在dev.html注入一些数据,而这些数据是异步获取的(调用接口或者其余形式)。我这里是须要通过Node.js的child_process执行git命令,获取分支信息。尝试templateParameters反对function。能够尝试传入async办法,通过await获取到数据后,再返回。遇到问题chainWebpack和templateParameters均不反对异步。 chainWebpack改为异步后,打包报错。templateParameters改为异步后,虽不影响打包,然而变量不失效。 苦于vue-cli-service和html-webpack-plugin均不反对异步,只能另想办法。 解决查阅材料先通过百度,国内貌似没有相似的记录文章。而后Google,果然找到了相似需要: Vue config async support 有人提出了相似的issue,尽管作者没有间接解决,然而也给出了一个work around: 改代码这个办法的思路,实际上将npm run serve包了一层wrap,在外层拿到数据后,再通过代码调用npm run serve开始打包。接下来咱们来实现这个wrap。 首先新建一个文件serve.prehandle.js,用于wrap。先获取异步数据,待拿到之后再开始打包: const getGitDevInfo = require('./src/pages/demo/webpack-plugin/git-info')const isDevEnv = process.env.NODE_ENV !== 'production'module.exports = (api, options) => { api.registerCommand('serve:prehandle', async (args) => { if (isDevEnv) { const def = await getGitDevInfo() process.asyncParamter = def } await api.service.run('serve', args) })}module.exports.defaultModes = { 'serve:prehandle': 'development'}而后批改package.json 批改serve命令 { "scripts": { "serve": "vue-cli-service serve:prehandle" }}减少vuePlugins,对应下面的serve.prehandle命令,为这条命令指定解决文件 ...

March 18, 2021 · 1 min · jiezi

将前后端交互同步化本篇封装了一下微信小程序的请求

今天自己写小程序的时候,近乎被异步搞到崩溃,不停地嵌套回调(我知道 await 和 promise,但是我嫌promise写起来跟裹脚布似的,而await我怕有兼容性问题也从来没有试过)言归正传,将小程序的异步调用变为同步(以下教程适用于所有异步,只是给小程序做了一下封装)。原理:增加事件队列,采用事件回调来完成同步化 以下代码复制粘贴到控制台即可测试效果;这里直接写es6代码了,先写个定时器版本的方便测试与理解先写个无注释版本的,方便直接看代码 class Async{ constructor() { this.list = []; this.sock = false; } request(obj) { setTimeout(() => { console.log(obj); this.sock = false; if(this.list[0]) this.do(this.list.shift()); }, 1000) } do(requestObj, async) { if(!async) { return this.request(requestObj); } if(this.sock) { this.list.push(requestObj); }else { this.sock = true; this.request(requestObj); } } }-----------以下为注释版本----------- class Async{ constructor() { this.list = []; // 定义 执行队列 this.sock = false; // 判断是否有任务正在执行 } request(obj) { setTimeout(() => { console.log(obj); this.sock = false; // 重置为没有任务正在执行 if(this.list[0]) // 如果队列中还有任务,执行下一个任务 this.do(this.list.shift()); }, 1000) // 模拟一个异步,一秒后执行任务,执行完成后执行下一个异步任务 } do(requestObj) { if(this.sock) // 如果有任务在执行 this.list.push(requestObj); // 将当前任务其增加到任务队列 else { this.sock = true; // 否则开始执行当前任务并设定'有任务在执行' this.request(requestObj); } } } var x = new Async(); x.do({url: 1}); // 一秒后打印 url: 1 x.do({url: 2}); // 两秒后打印 url: 2但是同步只是异步无可奈何的选择,所以不能全部否决掉异步 ...

July 14, 2019 · 2 min · jiezi

JavaScript-数组的高阶函数使用异步操作

JavaScript 数组的高阶函数使用异步操作吾辈的博客原文: https://blog.rxliuli.com/p/5e...场景吾辈是一只在飞向太阳的萤火虫JavaScript 中的数组是一个相当泛用性的数据结构,能当数组,元组,队列,栈进行操作,更好的是 JavaScript 提供了很多原生的高阶函数,便于我们对数组整体操作。然而,JavaScript 中的高阶函数仍有缺陷 -- 异步!当你把它们放在一起使用时,就会感觉到这种问题的所在。 例如现在,有一组 id,我们要根据 id 获取到远端服务器 id 对应的值,然后将之打印出来。那么,我们要怎么做呢? const wait = ms => new Promise(resolve => setTimeout(resolve, ms))async function get(id) { // 这里只是为了模拟每个请求的时间可能是不定的 await wait(Math.random() * id * 100) return '内容: ' + id.toString()}const ids = [1, 2, 3, 4]你或许会下意识地写出下面的代码 ids.forEach(async id => console.log(await get(id)))事实上,控制台输出是无序的,而并非想象中的 1, 2, 3, 4 依次输出 内容: 2 内容: 3 内容: 1 内容: 4这是为什么呢?原因便是 JavaScript 中数组的高阶函数并不会等待异步函数的返回!当你在网络上搜索时,会发现很多人会说可以使用 for-of, for-in 解决这个问题。 ...

June 23, 2019 · 9 min · jiezi

文件系统与异步操作异步IO那些破事

为什么想起写这篇文章前面这篇文章提到,旧的 Linux AIO 只支持直接(Direct)IO,还对读写区域大小有限制,但是 Windows 上的 IOCP 就有完整的 AIO 支持。之前真的觉得 Windows 真的很牛B,但是对为什么这样一直懵懵懂懂。 直到昨天我看到了这篇讨论帖:https://news.ycombinator.com/...,他说微软的异步IO是用线程模拟的。 WTF?这个内核原生支持这么高大上的东西居然是模拟的?但是人家还拿了微软官方的文章佐证 ... so if you issue an asynchronous cached read, and the pages are not in memory, the file system driver assumes that you do not want your thread blocked and the request will be handled by a limited pool of worker threads.微软官方的说明总不会错。但为什么会这样?缓冲(Buffered)IO实现异步为什么就这么难? 还得从硬件说起回顾一下大学学的计算机硬件知识:https://www.cnblogs.com/jswan... 每个硬盘有多个盘面,每个盘面都是由一圈圈的同心圆组成的,叫做磁道(track)。每个磁道又被等比划分为若干个弧段,叫做扇区(sector)。扇区有固定的大小和个数,而且从硬盘诞生就被分配在固定位置。一般一个扇区具体的大小视总磁盘大小而定,传统上512B为一个扇区(但是也有不同)。 扇区是实际上的磁盘最小读写单位,操作系统与磁盘文件系统通信必须以扇区的整数个进行。这里的整数个不仅代表大小,而且指个体,例如你不能只读第一个扇区的后半个+第二个扇区的前半个,虽然加起来大小也是一个扇区。 直接(Direct)IO,最原始的文件IO这种扇区操作磁盘的方式就直接派生了一种文件IO方式——直接(Direct)IO,也叫裸(Raw)IO,也叫非缓存(Unbuffered)IO。在 *nix 系中对应 O_DIRECT,在 Windows 中对应 FILE_FLAG_NO_BUFFERING。 ...

June 16, 2019 · 1 min · jiezi

JavaScript-异步时序问题

JavaScript 异步时序问题吾辈的博客原文:https://blog.rxliuli.com/p/de...场景死后我们必升天堂,因为活时我们已在地狱。不知你是否遇到过,向后台发送了多次异步请求,结果最后显示的数据却并不正确 -- 是旧的数据。 具体情况: 用户触发事件,发送了第 1 次请求用户触发事件,发送了第 2 次请求第 2 次请求成功,更新页面上的数据第 1 次请求成功,更新页面上的数据嗯?是不是感觉到异常了?这便是多次异步请求时会遇到的异步回调顺序与调用顺序不同的问题。 思考为什么会出现这种问题?出现这种问题怎么解决?为什么会出现这种问题?JavaScript 随处可见异步,但实际上并不是那么好控制。用户与 UI 交互,触发事件及其对应的处理函数,函数执行异步操作(网络请求),异步操作得到结果的时间(顺序)是不确定的,所以响应到 UI 上的时间就不确定,如果触发事件的频率较高/异步操作的时间过长,就会造成前面的异步操作结果覆盖后面的异步操作结果。 关键点 异步操作得到结果的时间(顺序)是不确定的如果触发事件的频率较高/异步操作的时间过长出现这种问题怎么解决?既然关键点由两个要素组成,那么,只要破坏了任意一个即可。 手动控制异步返回结果的顺序降低触发频率并限制异步超时时间手动控制返回结果的顺序根据对异步操作结果处理情况的不同也有三种不同的思路 后面异步操作得到结果后等待前面的异步操作返回结果后面异步操作得到结果后放弃前面的异步操作返回结果依次处理每一个异步操作,等待上一个异步操作完成之后再执行下一个这里先引入一个公共的 wait 函数 /** * 等待指定的时间/等待指定表达式成立 * 如果未指定等待条件则立刻执行 * 注: 此实现在 nodejs 10- 会存在宏任务与微任务的问题,切记 async-await 本质上还是 Promise 的语法糖,实际上并非真正的同步函数!!!即便在浏览器,也不要依赖于这种特性。 * @param param 等待时间/等待条件 * @returns Promise 对象 */function wait(param) { return new Promise(resolve => { if (typeof param === 'number') { setTimeout(resolve, param) } else if (typeof param === 'function') { const timer = setInterval(() => { if (param()) { clearInterval(timer) resolve() } }, 100) } else { resolve() } })}1. 后面异步操作得到结果后等待前面的异步操作返回结果/** * 将一个异步函数包装为具有时序的异步函数 * 注: 该函数会按照调用顺序依次返回结果,后面的调用的结果需要等待前面的,所以如果不关心过时的结果,请使用 {@link switchMap} 函数 * @param fn 一个普通的异步函数 * @returns 包装后的函数 */function mergeMap(fn) { // 当前执行的异步操作 id let id = 0 // 所执行的异步操作 id 列表 const ids = new Set() return new Proxy(fn, { async apply(_, _this, args) { const prom = Reflect.apply(_, _this, args) const temp = id ids.add(temp) id++ await wait(() => !ids.has(temp - 1)) ids.delete(temp) return await prom }, })}测试一下 ...

June 14, 2019 · 4 min · jiezi

总结异步编程的六种方式

异步编程众所周知 JavaScript 是单线程工作,也就是只有一个脚本执行完成后才能执行下一个脚本,两个脚本不能同时执行,如果某个脚本耗时很长,后面的脚本都必须排队等着,会拖延整个程序的执行。那么如何让程序像人类一样可以多线程工作呢?以下为几种异步编程方式的总结,希望与君共勉。 回调函数事件监听发布订阅模式PromiseGenerator (ES6)async (ES7)异步编程传统的解决方案:回调函数和事件监听 初始示例:假设有两个函数, f1 和 f2,f1 是一个需要一定时间的函数。 function f1() { setTimeout(function(){ console.log('先执行 f1') },1000)}function f2() { console.log('再执行 f2')}回调函数因为 f1 是一个需要一定时间的函数,所以可以将 f2 写成 f1 的回调函数,将同步操作变成异步操作,f1 不会阻塞程序的运行,f2 也无需空空等待,例如 JQuery 的 ajax。 回调函数的demo: function f1(f2){ setTimeout(function(){ console.log('先执行 f1') },1000) f2()}function f2() { console.log('再执行 f2')}效果如下: 总结:回调函数易于实现、便于理解,但是多次回调会导致代码高度耦合 事件监听脚本的执行不取决代码的顺序,而取决于某一个事件是否发生。 事件监听的demo $(document).ready(function(){ console.log('DOM 已经 ready')});发布订阅模式发布/订阅模式是利用一个消息中心,发布者发布一个消息给消息中心,订阅者从消息中心订阅该消息,。类似于 vue 的父子组件之间的传值。 发布订阅模式的 demo //订阅done事件$('#app').on('done',function(data){ console.log(data)})//发布事件$('#app').trigger('done,'haha')PromisePromise 实际就是一个对象, 从它可以获得异步操作的消息,Promise 对象有三种状态,pending(进行中)、fulfilled(已成功)和rejected(已失败)。Promise 的状态一旦改变之后,就不会在发生任何变化,将回调函数变成了链式调用。 Promise 封装异步请求demo export default function getMethods (url){ return new Promise(function(resolve, reject){ axios.get(url).then(res => { resolve(res) }).catch(err =>{ reject(err) }) })}getMethods('/api/xxx').then(res => { console.log(res)}, err => { console.log(err)})GeneratorGenerator 函数是一个状态机,封装了多个内部状态。执行 Generator 函数会返回一个遍历器对象,使用该对象的 next() 方法,可以遍历 Generator 函数内部的每一个状态,直到 return 语句。 ...

May 15, 2019 · 1 min · jiezi

Generator函数与async函数对比

Generator函数与async函数对比Generator函数:Generator函数是ES2015提供的异步解决方案,与普通函数有很大的不同;特征:在function关键字后面跟一个()号;在函数体内部使用yield表达式作为一个状态;Generator函数返回一个遍历器,可通过for……of方法遍历每个状态;用法:执行Generator并不立刻执行,返回一个遍历器,遍历器通过调用next()、throw()或者return()执行下一个状态、捕获错误或者结束遍历器;async函数:async函数是ES2017提供的异步函数语法,是generator的语法糖,但是用法上与Generator函数还是有很大不同;特征:在function关键字前面跟一个async关键字;在函数体内部使用await表达式;async函数返回一个promise对象;用法:执行async函数会立刻执行,和普通函数一样,但是返回一个promise对象;两者对比:Generator 出现在ES2015中,async 出现在ES2017中,async 是 Generator 的语法糖;执行方式不同,Generator 执行需要使用执行器(next()等方法);async 函数自带执行器,与普通函数的执行一样;async 的语法语义更加清楚,async 表示异步,await 表示等待;而 Generator 函数的()号和 yield 的语义就没那么直接了;Generator 中 yield 后面只能跟 Thunk 函数或 Promise 对象;而 async 函数中 await 后面可以是 promise 对象或者原始类型的值(会自动转为立即resovle的promise对象);返回值不同,Generator 返回遍历器,相比于 async 返回 promise 对象操作更加麻烦。参考:ECMAScript 6入门

April 3, 2019 · 1 min · jiezi

JavaScript ES6 async/await的简单学习demo

传统回调函数// demo1-callback.js/** 现在我们要做个事情,写个回调函数,每秒输出一个递增的数字,输出三次 普通回调函数的写法 /function logNumber(n, callback){ setTimeout(() => { console.log(n); n++; callback(n) }, 1000);}// 现在调用它logNumber(1, function(n){ logNumber(n, function(m){ logNumber(m, function(q){ }) })})Promise// demo2-promise.js/* 现在我们改用promise来重写demo1的函数 /// 我们在这里暴露那个promise以供demo3调用function generatorLogNumber(n){ return new Promise(res => { setTimeout(() => { console.log(n); n++; res(n) }, 1000); })}// 现在使用它generatorLogNumber(1) .then(n => { generatorLogNumber(n) .then(m => { generatorLogNumber(m) .then(q => { }) }) })// 这里把这个promise暴露出去以供demo3使用,记得把本demo的调用函数注释掉(就是15-24行注释掉)module.exports = generatorLogNumber;async/await// demo3-async-await.js/* 现在我们改用更加方便的async/await方式来调用demo2的promise */// 首先把那个promise引入进来const generatorLogNumber = require(’./demo2-promise.js’);(async () => {//双括号表示立即执行的匿名函数 const n = await generatorLogNumber(1); const m = await generatorLogNumber(n); const q = await generatorLogNumber(m);})()// 可以node demo3-async-await.js 来运行看看 ...

January 29, 2019 · 1 min · jiezi

tornado 源码之 coroutine 分析

tornado 源码之 coroutine 分析tornado 的协程原理分析 版本:4.3.0为支持异步,tornado 实现了一个协程库。tornado 实现的协程框架有下面几个特点:支持 python 2.7,没有使用 yield from特性,纯粹使用 yield 实现使用抛出异常的方式从协程返回值采用 Future 类代理协程(保存协程的执行结果,当携程执行结束时,调用注册的回调函数)使用 IOLoop 事件循环,当事件发生时在循环中调用注册的回调,驱动协程向前执行由此可见,这是 python 协程的一个经典的实现。本文将实现一个类似 tornado 实现的基础协程框架,并阐述相应的原理。外部库使用 time 来实现定时器回调的时间计算。 bisect 的 insort 方法维护一个时间有限的定时器队列。 functools 的 partial 方法绑定函数部分参数。 使用 backports_abc 导入 Generator 来判断函数是否是生成器。import timeimport bisectimport functoolsfrom backports_abc import Generator as GeneratorTypeFuture是一个穿梭于协程和调度器之间的信使。 提供了回调函数注册(当异步事件完成后,调用注册的回调)、中间结果保存、结束结果返回等功能add_done_callback 注册回调函数,当 Future 被解决时,改回调函数被调用。 set_result 设置最终的状态,并且调用已注册的回调函数协程中的每一个 yield 对应一个协程,相应的对应一个 Future 对象,譬如:@coroutinedef routine_main(): yield routine_simple() yield sleep(1)这里的 routine_simple() 和 sleep(1) 分别对应一个协程,同时有一个 Future 对应。class Future(object): def init(self): self._done = False self._callbacks = [] self._result = None def _set_done(self): self._done = True for cb in self._callbacks: cb(self) self._callbacks = None def done(self): return self._done def add_done_callback(self, fn): if self._done: fn(self) else: self._callbacks.append(fn) def set_result(self, result): self._result = result self._set_done() def result(self): return self._resultIOLoop这里的 IOLoop 去掉了 tornado 源代码中 IO 相关部分,只保留了基本需要的功能,如果命名为 CoroutineLoop 更贴切。这里的 IOLoop 提供基本的回调功能。它是一个线程循环,在循环中完成两件事:检测有没有注册的回调并执行检测有没有到期的定时器回调并执行程序中注册的回调事件,最终都会在此处执行。可以认为,协程程序本身、协程的驱动程序 都会在此处执行。协程本身使用 wrapper 包装,并最后注册到 IOLoop 的事件回调,所以它的从预激到结束的代码全部在 IOLoop 回调中执行。而协程预激后,会把 Runner.run() 函数注册到 IOLoop 的事件回调,以驱动协程向前运行。理解这一点对于理解协程的运行原理至关重要。这就是单线程异步的基本原理。因为都在一个线程循环中执行,我们可以不用处理多线程需要面对的各种繁琐的事情。coroutine协程返回值sleepRunner因为没有使用 yield from,协程无法直接返回值,所以使用抛出异常的方式返回。copyrightauthor:bigfish copyright: 许可协议 知识共享署名-非商业性使用 4.0 国际许可协议 ...

January 16, 2019 · 1 min · jiezi

Javascript中的异步编程

前言最近,小伙伴S 问了我一段代码:const funB = (value) => { console.log(“funB “+ value);};const funA = (callback) => { … setTimeout(() => { typeof callback === “function” && callback(“is_ok!”); }, 1000);}funA(funB);他不太理解这段代码中,funB 函数作为 funA 函数的参数这样的写法。从语义上看,callback 的意思是回调,那么是说 funB 是 funA 的回调嘛?我给他解释说,funB 函数的确是 funA 函数的回调,它会等待 funA 中前面的语句都执行完,再去执行。这是一种异步编程的写法。小伙伴S 还是有点不太理解。他问:异步编程是什么?除了回调函数之外,异步编程还有哪些?别急,让我们先从概念入手,再逐个理解异步编程中的方法,看看它的前世今生。什么是异步?所谓"异步”(Asynchronous),可以理解为一种不连续的执行。简单地说,就是把一个任务分成两段,先执行第一段,然后转而执行其他任务,等接到通知了,再回过头执行第二段。我们都知道,JavaScript是单线程的。而异步,对于JavaScript的重要性,则体现在非阻塞这一点上。一些常见的异步有:onclick 在其事件触发的时候,回调会立即添加到任务队列中。setTimeout 只有当时间到达的时候,才会将回调添加到任务队列中。ajax 在网络请求完成并返回之后,才将回调添加到任务队列中。接下来,我们一起来看看Javascript中的异步编程,具体有哪几种。实现异步编程的方法一、回调函数上面不止一次提到了回调函数。它从概念上说很简单,就是把任务的第二段单独写在一个函数里面,等到重新执行这个任务的时候,就直接调用这个函数。它是异步编程中,最基本的方法。举个例子,假定有两个函数 f1 和 f2,后者等待前者的执行结果。顺序执行的话,可以这样写:f1();f2();但是,如果 f1 是一个很耗时的任务,该怎么办?改写一下 f1,把 f2 写成 f1 的回调函数:const f1 = (callback) => { setTimeout(() => { typeof callback === “function” && callback(); }, 1000);}f1(f2);二、事件监听onclick 的写法,在异步编程中,称为事件监听。它的思路是:如果任务的执行不取决于代码的顺序,而取决于某个事件是否发生,也就事件驱动模式。还是 f1 和 f2 的例子,为了简化代码,这里采用jQuery的写法:// 为f1绑定一个事件,当f1发生done事件,就执行f2f1.on(‘done’, f2);// 改写f1function f1(){ setTimeout(() => { // f1的任务代码,执行完成后,立即触发done事件 f1.trigger(‘done’); }, 1000);}它的优点是:比较容易理解,耦合度降低了。可以绑定多个事件,而且每个事件还能指定多个回调函数。缺点是:整个程序都会变为由事件来驱动,流程会变得很不清晰。三、发布/订阅这是一种为了处理一对多的业务场景而诞生的设计模式,它也是一种异步编程的方法。vue中MVVM的实现,就有它的功劳。关于概念,我们可以这样理解,假定存在一个"信号中心”,某个任务执行完成,就向信号中心"发布"(publish)一个信号,其他任务可以向信号中心"订阅"(subscribe)这个信号,从而知道什么时候自己可以开始执行。这就叫做"发布/订阅模式"(publish-subscribe pattern),又称"观察者模式"(observer pattern)。下面的例子,采用的是 Morgan Roderick 的 PubSubJS ,这是一个无依赖的JavaScript插件:import PubSub from ‘pubsub-js’;// f2向 ‘PubSub’ 订阅信号 ‘done’PubSub.subscribe(‘done’, f2);const f1 = () => { setTimeout(() => { // f1执行完成后,向 ‘PubSub’ 发布信号 ‘done’,从而执行 f2 PubSub.publish(‘done’); }, 1000);};f1();// f2 完成执行后,也可以取消订阅PubSub.unsubscribe(“done”, f2);这种模式有点类似于“事件监听”,但是明显优于后者。因为,我们可以通过查看“消息中心”,了解存在多少信号、每个信号有多少订阅者,从而监控程序的运行。四、Promise对象接下来,我们聊聊与ajax相关的异步编程方法,Promise对象。Promise 是由 CommonJS 提出的一种规范,它是为了解决回调函数嵌套,也就是回调地狱的问题。它不是新的语法功能,而是一种新的写法,允许将回调函数的横向加载,改成纵向加载。它的思想是,每一个异步任务返回一个Promise对象,该对象有一个then方法,允许指定回调函数。继续改写 f1 和 f2:const f1 = () => { return new Promise((resolve, reject) => { let timeOut = Math.random() * 2; setTimeout(() => { if (timeOut < 1) { resolve(‘200 OK’); } else { reject(’timeout in ’ + timeOut + ’ seconds.’); } }, 1000); }); };const f2 = () => { console.log(‘start f2’); };f1().then((result) => { console.log(result); f2();}).catch((reason) => { …);例子中,用随机数模拟了请求的超时。当 f1 返回 Promise 的 resolve 时,执行 f2。Promise的优点是:回调函数变成了链式的写法,程序的流程可以看得很清楚。还有就是,如果一个任务已经完成,再添加回调函数,该回调函数会立即执行。所以,你不用担心是否错过了某个状态。缺点就是:编写和理解,都相对比较难。五、Generatorgenerator(生成器)是 ES6 标准引入的数据类型。它最大特点,就是可以交出函数的执行权(即暂停执行),是协程在 ES6 中的实现。看上去它像一个函数,定义如下:function* gen(x) { var y = yield x + 2; return y;}它不同于普通函数,函数名之前要加星号(*),是可以暂停执行的。整个 Generator 函数就是一个封装的异步任务,或者说是异步任务的容器。用 yield 语句注明异步操作需要暂停的地方。我们来看一下 Generator 函数执行的过程:var g = gen(1);// { value: 3, done: false }g.next();// { value: undefined, done: true }g.next();上面代码中,调用 Generator 函数,会返回一个内部指针(即遍历器 )g 。这是 Generator 函数不同于普通函数的另一个地方,即执行它不会返回结果,返回的是指针对象。调用指针 g 的 next 方法,会移动内部指针(即执行异步任务的第一段),指向第一个遇到的 yield 语句,上例是执行到 x + 2 为止。换言之,next 方法的作用是分阶段执行 Generator 函数。每次调用 next 方法,会返回一个对象,表示当前阶段的信息( value 属性和 done 属性)。value 属性是 yield 语句后面表达式的值,表示当前阶段的值;done 属性是一个布尔值,表示 Generator 函数是否执行完毕,即是否还有下一个阶段。六、async/await这是 ES8 中提出的一种更优雅的异步解决方案,灵感来自于 C# 语言。具体可前往 细说 async/await 相较于 Promise 的优势 ,深入理解其原理及特性。来看个例子,要实现一个暂停功能,输入 N 毫秒,则停顿 N 毫秒后才继续往下执行。const sleep = (time) => { return new Promise((resolve, reject) => { setTimeout(() => { resolve(); }, time); })};const start = async () => { console.log(‘start’); // 在这里使用起来就像同步代码那样直观 await sleep(1000); console.log(’end’);};start();控制台先输出 start,稍等 1 秒后,输出结果 ok,最后输出 end。解析一下上述代码:async 表示这是一个async函数,await 只能用在这个函数里面。await 表示在这里等待 promise 返回了结果,再继续执行。使用起来,就像写同步代码一样地优雅。总结JavaScript的异步编写方式,从 回调函数 到 async/await,感觉在写法上,每次都有进步,其本质就是一次次对语言层抽象的优化。以至于现在,我们可以像同步一样地,去处理异步。换句话说就是:异步编程的最高境界,就是根本不用关心它是不是异步。PS:欢迎关注我的公众号 “超哥前端小栈”,交流更多的想法与技术。 ...

January 14, 2019 · 2 min · jiezi

promise, async, await, execution order

async can be transformed to promise. So, if we want to understand async, we have to understand promise first.PromiseNormally, promise is easy to understand, especially when using like this:promise .then(() => { // }) .then(() => { // }) .then(() => { // })then after then would make the order of async callbacks clear. Actually we shouldn’t rely on async callbacks order. But a certain execution order would make me feel more comfortable. However, sometimes, things are different.RESOLVE and Promise.resolve()Normally, I initialize a promise by Promise.resolve() because it seems too troublesome to use Promise constructor like below which I called it RESOLVE in this article.new Promise((resolve,reject)=>{ resolve()})And normally, I used it by Promise.resolve(non-thenable) which is equivalent to RESOLVE(non-thenable)new Promise((resolve, reject) => { resolve(non-thenable)})So, it doesn’t matter which one you choose. RESOLVE(non-thenable) or Promise.resolve(non-thenable). However, when it comes to thenable, things are different. Promise.resolve(thenable) is not equivalent to RESOLVE(thenable)new Promise((resolve, reject) => { resolve(thenable)})I explained it carefully in What’s the difference between resolve(promise) and resolve(’non-thenable-object’)?. And here is the conclusion:for non-thenable, Promise.resolve(non-thenable) is equivalent to RESOLVE(non-thenable)for thenable, Promise.resolve(thenable) is not equivalent to RESOLVE(thenable) because RESOLVE(thenable)new Promise((resolve, reject) => { resolve(thenable)})is equivalent tonew Promise((resolve, reject) => { Promise.resolve().then(() => { thenable.then(resolve) })})according to spec. It’s obviously not equivalent to Promise.resolve(thenable). You can test it by this example:let p1 = Promise.resolve(1)Promise.resolve(p1).then(res => { console.log(res)})p1.then(res => { console.log(2)})//1//2whilelet p1 = Promise.resolve(1)new Promise((resolve, reject) => { resolve(p1)}).then(res => { console.log(res)})p1.then(res => { console.log(2)})//2//1So, here comes another question. When would we use Promise.resolve(thenable) or RESOLVE(thenable)? It doesn’t seem to be that common.Yes, indeed. Except async and await.async and awaitAs we all know or spec says that the result of async returns promise. For example:(async function(){}()).toString() //"[object Promise]“And await can be used in async.awaitSo, how does await work in async? According to spec:Await:We can transform await codeconst p1 = Promise.resolve(1)const async1 = async function() { const res1 = await p1 console.log(res1)}async1()p1.then(() => console.log(‘after gen’))toconst p1 = Promise.resolve(1)const async1 = async function() { new Promise(resolve => { resolve(p1) }).then(res => { const res1 = res console.log(res1) })}async1()p1.then(() => console.log(‘after gen’))The result is the same:after gen1on chrome 70. However, in chrome canary 73 the former result is1after genWhy? The reason can be found in https://github.com/tc39/ecma2… Simply say, the spec to await was going to change to:What’s difference? The difference is exactly the difference between RESOLVE(thenable) and Promise.resolve(thenable).In the past and chrome 70, we are using RESOLVE(thenable), so I transformed the code like above. If using this new spec, the code should be transformed toconst p1 = Promise.resolve(1)const async1 = async function() { Promise.resolve(p1).then(res => { const res1 = res console.log(res1) })}async1()p1.then(() => console.log(‘after gen’))In this case, chrome 70 and canary 73 would all get1after genThat’s the spec change for await. Hope you both understand the way before and after change.asyncNow, let’s talk about async. How does async work? According to spec:The spawn used in the above desugaring is a call to the following algorithm. This algorithm does not need to be exposed directly as an API to user code, it is part of the semantics of async functions.And the spawn isfunction spawn (genF, self) { return new Promise(function (resolve, reject) { var gen = genF.call(self) function step (nextF) { var next try { next = nextF() } catch (e) { // finished with failure, reject the promise reject(e) return } if (next.done) { // finished with success, resolve the promise resolve(next.value) return } // not finished, chain off the yielded promise and step again Promise.resolve(next.value).then( function (v) { step(function () { return gen.next(v) }) }, function (e) { step(function () { return gen.throw(e) }) } ) } step(function () { return gen.next(undefined) }) })}However, I think the spawn is the future version which doesn’t apply to chrome 70 because it used Promise.resolve(next.value) instead of RESOLVE(next.value) to transform await. So, I thought the old version or version applied to chrome 70 should befunction spawn (genF, self) { return new Promise(function (resolve, reject) { var gen = genF.call(self) function step (nextF) { var next try { next = nextF() } catch (e) { // finished with failure, reject the promise reject(e) return } if (next.done) { // finished with success, resolve the promise resolve(next.value) return } // not finished, chain off the yielded promise and step again /* modified line / new Promise(resolve => resolve(next.value)).then( / origin line / // Promise.resolve(next.value).then( function (v) { step(function () { return gen.next(v) }) }, function (e) { step(function () { return gen.throw(e) }) } ) } step(function () { return gen.next(undefined) }) })}You can tested it by comparing the result of below example.const p1 = Promise.resolve(1)const p2 = Promise.resolve(2)const async1 = async function () { const res1 = await p1 console.log(res1) const res2 = await p2 console.log(res2)}async1()p1.then(() => console.log(‘after gen’))withconst p1 = Promise.resolve(1)const p2 = Promise.resolve(2)const gen = function () { const res1 = yield p1 console.log(res1) const res2 = yield p2 console.log(res2)}const async1Eq = function () { spawn(gen, this)}async1Eq()p1.then(() => console.log(‘after gen’))The result would be:On chrome 70, with the former spawn, you will get the different result. While you will get the same result with the latter spawn.In the same way, on chrome 73, with the former spawn, you will get the same result. While you will get the different result with the latter spawn.Origin Post ...

December 27, 2018 · 5 min · jiezi

前端er,你真的会用 async 吗?

async 异步函数 不完全使用攻略前言现在已经到 8012 年的尾声了,前端各方面的技术发展也层出不穷,VueConf TO 2018 大会 也发布了 Vue 3.0的计划。而在我们(我)的日常中也经常用 Vue 来编写一些项目。那么,就少不了 ES6 的登场了。那么话说回来,你真的会用 ES6 的 async 异步函数吗?1、async 介绍先上 MDN 介绍:https://developer.mozilla.org…async function 用于声明 一个 返回 AsyncFunction 对象的异步函数。异步函数是值通过事件循环异步执行的函数,它会通过一个隐式的 Promise 返回其结果。如果你的代码使用了异步函数,它的语法和结构更像是标准的同步函数人工翻译:async 关键字是用于表示一个函数里面有异步操作的含义。它通过返回一个 Promise 对象来返回结果它的最大的特点是:通过 async / await 将异步的操作,但是写法和结构却是和我们平时写的(同步代码)是一样2、示范// 一般我们会把所有请求方法都定义在一个文件里,这里定义一个方法来模拟我们的日常请求function fetch() { axios.get(’/user?ID=12345’) .then(function (response) { console.log(response); }) .catch(function (error) { console.log(error); });};// 然后在需要它的地方调用它async function getUserInfo() { const info = await fetch(); return info;}getUserInfo().then(info => console.log(info));我们可以看到,整个过程非常直观和清晰,语句语义非常明确,整个异步操作看起来就像是同步一样。如果看完上面的流程没有问题的话,那我们接下来继续深入的了解一下。3、async Promise setTimeout(定时器) 的结合使用情况接下来给大家演示一道题目,这道题是我当时面某条的面试题,估计和多人也见过,这道题非常经典而且使用场景页非常多,研究意义非常大,那么我在这里就给大家分享一下。求下面的输出结果:async function async1(){ console.log(‘async1 start’) await async2() console.log(‘async1 end’)}async function async2(){ console.log(‘async2’)}console.log(‘script start’)setTimeout(function(){ console.log(‘setTimeout’)},0) async1();new Promise(function(resolve){ console.log(‘promise1’) resolve();}).then(function(){ console.log(‘promise2’)})console.log(‘script end’)这里一共有 8 条 log 语句,先别复制到控制台上,大家给20秒钟的时间默念一下输出的顺序。1..2.. .. .. 20我先给上正确的答案:script startasync1 startasync2promise1script endpromise2async1 endsetTimeout如果你的答案和上面的正确答案有所偏差,那么说明你对 async / await 的理解还是不够深刻,希望你阅读完我的这篇文章之后可以直面各种同步异步问题了(嘻嘻,这还不点个赞嘛)我们再来回顾一下 MDN 对 async / await 的描述:当调用一个 async 函数时,会返回一个 Promise 对象。当这个 async 函数返回一个值时,Promise 的 resolve 方法会负责传递这个值;当 async 函数抛出异常时,Promise 的 reject 方法也会传递这个异常值。async 函数中可能会有 await 表达式,这会使 async 函数暂停执行,等待 Promise 的结果出来,然后恢复async函数的执行并返回解析值(resolved)。async/await的用途是简化使用 promises 异步调用的操作,并对一组 Promises执行某些操作。正如Promises类似于结构化回调,async/await类似于组合生成器和 promises。awaitawait 操作符用于等待一个Promise 对象。它只能在异步函数 async function 中使用。[return_value] = await expression;await 表达式会暂停当前 async function 的执行,等待 Promise 处理完成。若 Promise 正常处理(fulfilled),其回调的resolve函数参数作为 await 表达式的值,继续执行 async function。若 Promise 处理异常(rejected),await 表达式会把 Promise 的异常原因抛出。另外,如果 await 操作符后的表达式的值不是一个 Promise,则返回该值本身。其中非常重要的一句是:遇到 await 表达式时,会让 async 函数 暂停执行,等到 await 后面的语句(Promise)状态发生改变(resolved或者rejected)之后,再恢复 async 函数的执行(再之后 await 下面的语句),并返回解析值(Promise的值)这么多 Promise 相关的内容是因为async / await 是建立在 Promise 的基础上的呀~~然后再来回头看我们的题目,会发现,有点不对劲啊async1 endpromise2那是因为还有一个Promise.resolve 的点没有考虑,这也是我中招的点4、分析过程定义一个异步函数 async1定义一个异步函数 async2打印 ‘script start’ // *1定义一个定时器(宏任务,优先级低于微任务),在0ms 之后输出执行异步函数 async1打印 ‘async1 start’ // *2遇到await 表达式,执行 await 后面的 async2打印 ‘async2’ // *3返回一个 Promise,跳出 async1 函数体执行 new Promise 里的语句打印 ‘promise1‘ // *4resolve() , 返回一个 Promise 对象,把这个 Promise 压进队列里打印 ’script end’ // *5同步栈执行完毕回到 async1 的函数体,async2 函数没有返回 Promise,所以把要等async2 的值 resolve,把 Promise 压进队列执行 new Promise 后面的 .then,打印 ’promise2‘ // *6回到 async1 的函数体,await 返回 Promise.resolve() ,然后打印后面的 ’async1 end‘ // *7最后执行定时器(宏任务) setTimeout,打印 ’setTimeout‘ // *8我对这段代码的过程分析大致如上(如果有什么理解不对的地方请指出),这里有很关键而且是大家容易理解错误的点是:很多人以为 await 会一直等待后面的表达式执行完之后才会执行后续代码,实际上 await 是会先执行后面的表达式,然后返回一个Promise,接着就跳出整个 async 函数来执行后面的代码,也就是说执行到 await 的时候,会有一个 让出线程 的操作。等后面的同步站执行完了之后,又会回到 async 函数中等待 await 表达式的返回值,如果不是一个 Promise 对象,则会有一个期待它 resolve 成为一个 Promise对象的过程,然后继续执行 async 函数后面的代码,直到是一个 Promise 对象,则把这个 Promise 对象放入 Promise 队列里。所以说 ,’async1 end’ 和‘promise2‘ 这个不注意就会出错的难点就是这样那么现在,我们是不是大致上对async / await 理解了呢,我们来改一下这道题再来看看,把 async2 改造一下async function async1(){ console.log(‘async1 start’) await async2() console.log(‘async1 end’)}function async2(){ // 去掉了 async 关键字 console.log(‘async2’);}console.log(‘script start’)setTimeout(function(){ console.log(‘setTimeout’)},0) async1();new Promise(function(resolve){ console.log(‘promise1’) resolve();}).then(function(){ console.log(‘promise2’)})console.log(‘script end’)这次大家能做对了吗~5、日常常用示例上面写了那么多,只是为了方便大家对于异步函数的理解,下面给一些我们日常开发中使用异步函数的例子。一般来说,我们有一个业务需要分不完成,每个步骤都是异步的,并且严重依赖于上一步的执行结果,稍有不慎就会进入回调地狱(callback hell)了,这种情况下,我们可以用 async / await 来完成// 比如在这里场景,我们提交数据的时候先判定用户是否有这个权限,然后再进行下一步动作async function submitData(data) { const res = await getAuth(); // 获取授权状态 if (res….) { const data = await submit(data); } toast(data.message);}这样就可以保证两个操作的先后顺序或者是在 Vue 中,一些初始化的操作async created() { const res = await this.init(); // 获取列表等操作 const list = await this.getPage(); // 分页请求等}但是在使用过程中,我们会发现刚从回调地狱中解救,然后就陷入 async / await 地狱的诞生举一个例子:async created() { const userInfo = await this.getUserInfo(); // 获取用户数据 const list = await this.getNewsList(); // 获取文章数据}表面上看,这段语法是正确的,但并不是一个优秀实现,因为它把两个没有先后顺序的一部操作强行变成同步操作了,因为这里的代码是一行接着一行执行的,想一下,我们没有必要在获取用户数据之后才去获取文章数据,它们的工作是可以同时进行的这里给出一些常用的并发执行的实例async created() { const userInfo = this.getUserInfo(); // 它们都会返回 Promise 对象 const list = this.getNewsList(); await userInfo; await list; // …do something}// 如果有很多请求的情况下可以使用 Promise.allasync created() { Promise.all([this.getUserInfo(), this.getNewsList()]).then(()=> { // …do something });}5、图例6、小结1、异步的终极解决方案2、看起来像同步的异步操作3、便捷的捕获错误和调试4、支持并发执行5、要知道避免 async / await 地狱7、写在最后好了,关于async 异步函数的不完全指南就说到这里了,上面所提及的内容,可能也就比较浅显的内容。而且有时候,建议大家熟练使用它,在日常开发中多使用多总结才会有沉淀的效果,都是要靠自己多练,才能熟悉使用,熟能生巧!最后,如果大家觉得我有哪里写错了,写得不好,有其它什么建议(夸奖),非常欢迎大家补充。希望能让大家交流意见,相互学习,一起进步!我是一名 19 的应届新人,以上就是今天的分享,新手上路中,后续不定期周更(或者是月更哈哈),我会努力让自己变得更优秀、写出更好的文章,文章中有不对之处,烦请各位大神斧正。如果你觉得这篇文章对你有所帮助,请记得点赞或者品论留言哦~。 ...

December 3, 2018 · 2 min · jiezi

[译]await VS return VS return await

原文地址:await vs return vs return await作者:Jake Archibald当编写异步函数的时候,await,return,return await三者之间有一些区别,从中选取正确的方式是很重要的。我们从下面这个异步函数开始:async function waitAndMaybeReject(){ // 等待1秒钟 await new Promise(resolve => setTimeout(resolve, 1000)); // 抛一枚硬币 const isHeads = Boolean(Math.round(Math.random())); if(isHeads) return ‘yay’; throw Error(‘Boo!’);}上面的函数会等待1秒钟后返回一个promise,然后有50%的机会成功返回yay或者抛出一个error。让我们用几种稍微不同的方式使用它。直接调用async function foo() { try{ waitAndMaybeReject(); }catch(e){ return ‘caught’; }}在此处,如果调用了foo,返回的promise的状态始终都是resolved,值也永远是undefined,而且没有等待。由于我们没有await,或者return waitAndMaybeReject()的结果,所以我们无法对它做出任何反应。像这样的代码通常是错误的。Awaitingasync function foo(){ try{ await waitAndMaybeReject(); }catch(e){ return ‘caught’; }}在此处,如果调用了foo,返回的promise将始终等待1秒钟,然后结果要么状态为resolved,值为undefined,要么状态为resolved,值为"caught"。因为我们等待了waitAndMaybeReject()的返回值,所以它的rejection会被返回并且被抛出(throw),catch的代码块就会执行。但无论如何,如果waitAndMaybeReject()没有报错而是顺利执行,我们依旧无法对它的返回值做任何事情。Returningasync function foo() { try { return waitAndMaybeReject(); } catch (e) { return ‘caught’; }}在此处,如果调用了foo,返回的promise将始终等待1秒钟,然后结果要么是状态为resolved,值为"yaa",要么是状态是reject,抛出错误Error(‘Boo!’)。通过return waitAndMaybeReject()这行代码,我们直接传递了它的返回结果,所以我们的catch代码块永远不会执行。Return-awaiting如果你想在try代码块中得到带有正确返回值的resolved状态,在catch中捕获异常,那么正确的选择就是return await。async function foo() { try { return await waitAndMaybeReject(); } catch (e) { return ‘caught’; }}在此处,如果调用foo,返回的promise将始终等待1秒钟,然后结果要么是状态为resolved,值为"yay",要么是状态为resolved,值为"caught"因为我们等待了waitAndMaybeReject()的结果,所以它的异常rejecttion会被返回并且被抛出(throw),catch的代码块就会执行。如果waitAndMaybeReject()顺利执行没有报错,就返它的结果。如果对上面的内容还是觉着困惑,那么将代码拆分成两个步骤来看可能会比较好理解:async function foo() { try { // 等待 waitAndMaybeReject() 的结果来解决, // 并且将 fullfill 的值赋给 fullfilledValue: const fulfilledValue = await waitAndMaybeReject(); // 如果 waitAndMaybeReject() reject了, // 我们的代码就会抛出异常,并且进入 catch 代码块的逻辑。 // 否则,这里的代码就会继续运行下面的语句: return fulfilledValue; } catch (e) { return ‘caught’; }}Note: 在try/catch之外的代码块中执行return await是多余的(如前所述,直接return即可),甚至Eslint还专门有规则来检测这种场景,但是在try/catch代码块之内,Eslint就允许这种操作。 ...

November 23, 2018 · 1 min · jiezi

ES6 系列之 Babel 将 Async 编译成了什么样子

前言本文就是简单介绍下 Async 语法编译后的代码。Asyncconst fetchData = (data) => new Promise((resolve) => setTimeout(resolve, 1000, data + 1))const fetchValue = async function () { var value1 = await fetchData(1); var value2 = await fetchData(value1); var value3 = await fetchData(value2); console.log(value3)};fetchValue();// 大约 3s 后输出 4Babel我们直接在 Babel 官网的 Try it out 粘贴上述代码,然后查看代码编译成什么样子:“use strict”;function _asyncToGenerator(fn) { return function() { var gen = fn.apply(this, arguments); return new Promise(function(resolve, reject) { function step(key, arg) { try { var info = genkey; var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then( function(value) { step(“next”, value); }, function(err) { step(“throw”, err); } ); } } return step(“next”); }); };}var fetchData = function fetchData(data) { return new Promise(function(resolve) { return setTimeout(resolve, 1000, data + 1); });};var fetchValue = (function() { var _ref = _asyncToGenerator( /#PURE/ regeneratorRuntime.mark(function _callee() { var value1, value2, value3; return regeneratorRuntime.wrap( function _callee$(_context) { while (1) { switch ((_context.prev = _context.next)) { case 0: _context.next = 2; return fetchData(1); case 2: value1 = _context.sent; _context.next = 5; return fetchData(value1); case 5: value2 = _context.sent; _context.next = 8; return fetchData(value2); case 8: value3 = _context.sent; console.log(value3); case 10: case “end”: return _context.stop(); } } }, _callee, this ); }) ); return function fetchValue() { return _ref.apply(this, arguments); };})();fetchValue();_asyncToGeneratorregeneratorRuntime 相关的代码我们在 《ES6 系列之 Babel 将 Generator 编译成了什么样子》 中已经介绍过了,这次我们重点来看看 _asyncToGenerator 函数:function _asyncToGenerator(fn) { return function() { var gen = fn.apply(this, arguments); return new Promise(function(resolve, reject) { function step(key, arg) { try { var info = genkey; var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then( function(value) { step(“next”, value); }, function(err) { step(“throw”, err); } ); } } return step(“next”); }); };}以上这段代码主要是用来实现 generator 的自动执行以及返回 Promise。当我们执行 fetchValue() 的时候,执行的其实就是 _asyncToGenerator 返回的这个匿名函数,在匿名函数中,我们执行了var gen = fn.apply(this, arguments);这一步就相当于执行 Generator 函数,举个例子:function* helloWorldGenerator() { yield ‘hello’; yield ‘world’; return ’ending’;}var hw = helloWorldGenerator();var gen = fn.apply(this, arguments) 就相当于 var hw = helloWorldGenerator();,返回的 gen 是一个具有 next()、throw()、return() 方法的对象。然后我们返回了一个 Promise 对象,在 Promise 中,我们执行了 step(“next”),step 函数中会执行:try { var info = genkey; var value = info.value;} catch (error) { reject(error); return;}step(“next”) 就相当于 var info = gen.next(),返回的 info 对象是一个具有 value 和 done 属性的对象:{value: Promise, done: false}接下来又会执行:if (info.done) { resolve(value);} else { return Promise.resolve(value).then( function(value) { step(“next”, value); }, function(err) { step(“throw”, err); } );}value 此时是一个 Promise,Promise.resolve(value) 依然会返回这个 Promise,我们给这个 Promise 添加了一个 then 函数,用于在 Promise 有结果时执行,有结果时又会执行 step(“next”, value),从而使得 Generator 继续执行,直到 info.done 为 true,才会 resolve(value)。不完整但可用的代码(function() { var ContinueSentinel = {}; var mark = function(genFun) { var generator = Object.create({ next: function(arg) { return this._invoke(“next”, arg); } }); genFun.prototype = generator; return genFun; }; function wrap(innerFn, outerFn, self) { var generator = Object.create(outerFn.prototype); var context = { done: false, method: “next”, next: 0, prev: 0, sent: undefined, abrupt: function(type, arg) { var record = {}; record.type = type; record.arg = arg; return this.complete(record); }, complete: function(record, afterLoc) { if (record.type === “return”) { this.rval = this.arg = record.arg; this.method = “return”; this.next = “end”; } return ContinueSentinel; }, stop: function() { this.done = true; return this.rval; } }; generator._invoke = makeInvokeMethod(innerFn, context); return generator; } function makeInvokeMethod(innerFn, context) { var state = “start”; return function invoke(method, arg) { if (state === “completed”) { return { value: undefined, done: true }; } context.method = method; context.arg = arg; while (true) { state = “executing”; if (context.method === “next”) { context.sent = context._sent = context.arg; } var record = { type: “normal”, arg: innerFn.call(self, context) }; if (record.type === “normal”) { state = context.done ? “completed” : “yield”; if (record.arg === ContinueSentinel) { continue; } return { value: record.arg, done: context.done }; } } }; } window.regeneratorRuntime = {}; regeneratorRuntime.wrap = wrap; regeneratorRuntime.mark = mark;})();“use strict”;function _asyncToGenerator(fn) { return function() { var gen = fn.apply(this, arguments); return new Promise(function(resolve, reject) { function step(key, arg) { try { var info = genkey; var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then( function(value) { step(“next”, value); }, function(err) { step(“throw”, err); } ); } } return step(“next”); }); };}var fetchData = function fetchData(data) { return new Promise(function(resolve) { return setTimeout(resolve, 1000, data + 1); });};var fetchValue = (function() { var _ref = _asyncToGenerator( /#PURE/ regeneratorRuntime.mark(function _callee() { var value1, value2, value3; return regeneratorRuntime.wrap( function _callee$(_context) { while (1) { switch ((_context.prev = _context.next)) { case 0: _context.next = 2; return fetchData(1); case 2: value1 = _context.sent; _context.next = 5; return fetchData(value1); case 5: value2 = _context.sent; _context.next = 8; return fetchData(value2); case 8: value3 = _context.sent; console.log(value3); case 10: case “end”: return _context.stop(); } } }, _callee, this ); }) ); return function fetchValue() { return _ref.apply(this, arguments); };})();fetchValue();请原谅我水了一篇文章……ES6 系列ES6 系列目录地址:https://github.com/mqyqingfeng/BlogES6 系列预计写二十篇左右,旨在加深 ES6 部分知识点的理解,重点讲解块级作用域、标签模板、箭头函数、Symbol、Set、Map 以及 Promise 的模拟实现、模块加载方案、异步处理等内容。如果有错误或者不严谨的地方,请务必给予指正,十分感谢。如果喜欢或者有所启发,欢迎 star,对作者也是一种鼓励。 ...

October 31, 2018 · 4 min · jiezi

ES6 系列之异步处理实战

前言我们以查找指定目录下的最大文件为例,感受从回调函数 -> Promise -> Generator -> Async异步处理方式的改变。API 介绍为了实现这个功能,我们需要用到几个 Nodejs 的 API,所以我们来简单介绍一下。fs.readdirreaddir 方法用于读取目录,返回一个包含文件和目录的数组。fs.statstat 方法的参数是一个文件或目录,它产生一个对象,该对象包含了该文件或目录的具体信息。此外,该对象还有一个 isFile() 方法可以判断正在处理的到底是一个文件,还是一个目录。思路分析我们基本的实现思路就是:用 fs.readdir 获取指定目录的内容信息循环遍历内容信息,使用 fs.stat 获取该文件或者目录的具体信息将具体信息储存起来当全部储存起来后,筛选其中的是文件的信息遍历比较,找出最大文件获取并返回最大文件然后我们直接上代码吧。回调函数var fs = require(‘fs’);var path = require(‘path’);function findLargest(dir, cb) { // 读取目录下的所有文件 fs.readdir(dir, function(er, files) { if (er) return cb(er); var counter = files.length; var errored = false; var stats = []; files.forEach(function(file, index) { // 读取文件信息 fs.stat(path.join(dir, file), function(er, stat) { if (errored) return; if (er) { errored = true; return cb(er); } stats[index] = stat; // 事先算好有多少个文件,读完 1 个文件信息,计数减 1,当为 0 时,说明读取完毕,此时执行最终的比较操作 if (–counter == 0) { var largest = stats .filter(function(stat) { return stat.isFile() }) .reduce(function(prev, next) { if (prev.size > next.size) return prev return next }) cb(null, files[stats.indexOf(largest)]) } }) }) })}使用方式为:// 查找当前目录最大的文件findLargest(’./’, function(er, filename) { if (er) return console.error(er) console.log(’largest file was:’, filename)});Promisevar fs = require(‘fs’);var path = require(‘path’);var readDir = function(dir) { return new Promise(function(resolve, reject) { fs.readdir(dir, function(err, files) { if (err) reject(err); resolve(files) }) })}var stat = function(path) { return new Promise(function(resolve, reject) { fs.stat(path, function(err, stat) { if (err) reject(err) resolve(stat) }) })}function findLargest(dir) { return readDir(dir) .then(function(files) { let promises = files.map(file => stat(path.join(dir, file))) return Promise.all(promises).then(function(stats) { return { stats, files } }) }) .then(data => { let largest = data.stats .filter(function(stat) { return stat.isFile() }) .reduce((prev, next) => { if (prev.size > next.size) return prev return next }) return data.files[data.stats.indexOf(largest)] })}使用方式为:findLargest(’./’).then(function(filename) { console.log(’largest file was:’, filename);}).catch(function() { console.log(error);});Generatorvar fs = require(‘fs’);var path = require(‘path’);var co = require(‘co’)var readDir = function(dir) { return new Promise(function(resolve, reject) { fs.readdir(dir, function(err, files) { if (err) reject(err); resolve(files) }) })}var stat = function(path) { return new Promise(function(resolve, reject) { fs.stat(path, function(err, stat) { if (err) reject(err) resolve(stat) }) })}function* findLargest(dir) { var files = yield readDir(dir); var stats = yield files.map(function(file) { return stat(path.join(dir, file)) }) let largest = stats .filter(function(stat) { return stat.isFile() }) .reduce((prev, next) => { if (prev.size > next.size) return prev return next }) return files[stats.indexOf(largest)]}使用方式为:co(findLargest, ‘./’).then(function(filename) { console.log(’largest file was:’, filename);}).catch(function() { console.log(error);});Asyncvar fs = require(‘fs’);var path = require(‘path’);var readDir = function(dir) { return new Promise(function(resolve, reject) { fs.readdir(dir, function(err, files) { if (err) reject(err); resolve(files) }) })}var stat = function(path) { return new Promise(function(resolve, reject) { fs.stat(path, function(err, stat) { if (err) reject(err) resolve(stat) }) })}async function findLargest(dir) { var files = await readDir(dir); let promises = files.map(file => stat(path.join(dir, file))) var stats = await Promise.all(promises) let largest = stats .filter(function(stat) { return stat.isFile() }) .reduce((prev, next) => { if (prev.size > next.size) return prev return next }) return files[stats.indexOf(largest)]}使用方式为:findLargest(’./’).then(function(filename) { console.log(’largest file was:’, filename);}).catch(function() { console.log(error);});ES6 系列ES6 系列目录地址:https://github.com/mqyqingfeng/BlogES6 系列预计写二十篇左右,旨在加深 ES6 部分知识点的理解,重点讲解块级作用域、标签模板、箭头函数、Symbol、Set、Map 以及 Promise 的模拟实现、模块加载方案、异步处理等内容。如果有错误或者不严谨的地方,请务必给予指正,十分感谢。如果喜欢或者有所启发,欢迎 star,对作者也是一种鼓励。 ...

October 25, 2018 · 3 min · jiezi

Async:简洁优雅的异步之道

前言在异步处理方案中,目前最为简洁优雅的便是async函数(以下简称A函数)。经过必要的分块包装后,A函数能使多个相关的异步操作如同同步操作一样聚合起来,使其相互间的关系更为清晰、过程更为简洁、调试更为方便。它本质是Generator函数的语法糖,通俗的说法是使用G函数进行异步处理的增强版。尝试学习A函数必须有Promise基础,最好还了解Generator函数,有需要的可查看延伸小节。为了直观的感受A函数的魅力,下面使用Promise和A函数进行了相同的异步操作。该异步的目的是获取用户的留言列表,需要分页,分页由后台控制。具体的操作是:先获取到留言的总条数,再更正当前需要显示的页数(每次切换到不同页时,总数目可能会发生变化),最后传递参数并获取到相应的数据。let totalNum = 0; // Total comments number.let curPage = 1; // Current page index.let pageSize = 10; // The number of comment displayed in one page.// 使用A函数的主代码。async function dealWithAsync() { totalNum = await getListCount(); console.log(‘Get count’, totalNum); if (pageSize * (curPage - 1) > totalNum) { curPage = 1; } return getListData();}// 使用Promise的主代码。function dealWithPromise() { return new Promise((resolve, reject) => { getListCount().then(res => { totalNum = res; console.log(‘Get count’, res); if (pageSize * (curPage - 1) > totalNum) { curPage = 1; } return getListData() }).then(resolve).catch(reject); });}// 开始执行dealWithAsync函数。// dealWithAsync().then(res => {// console.log(‘Get Data’, res)// }).catch(err => {// console.log(err);// });// 开始执行dealWithPromise函数。// dealWithPromise().then(res => {// console.log(‘Get Data’, res)// }).catch(err => {// console.log(err);// });function getListCount() { return createPromise(100).catch(() => { throw ‘Get list count error’; });}function getListData() { return createPromise([], { curPage: curPage, pageSize: pageSize, }).catch(() => { throw ‘Get list data error’; });}function createPromise( data, // Reback data params = null, // Request params isSucceed = true, timeout = 1000,) { return new Promise((resolve, reject) => { setTimeout(() => { isSucceed ? resolve(data) : reject(data); }, timeout); });}对比dealWithAsync和dealWithPromise两个简单的函数,能直观的发现:使用A函数,除了有await关键字外,与同步代码无异。而使用Promise则需要根据规则增加很多包裹性的链式操作,产生了太多回调函数,不够简约。另外,这里分开了每个异步操作,并规定好各自成功或失败时传递出来的数据,近乎实际开发。1 登堂1.1 形式A函数也是函数,所以具有普通函数该有的性质。不过形式上有两点不同:一是定义A函数时,function关键字前需要有async关键字(意为异步),表示这是个A函数。二是在A函数内部可以使用await关键字(意为等待),表示会将其后面跟随的结果当成异步操作并等待其完成。以下是它的几种定义方式。// 声明式async function A() {}// 表达式let A = async function () {};// 作为对象属性let o = { A: async function () {}};// 作为对象属性的简写式let o = { async A() {}};// 箭头函数let o = { A: async () => {}};1.2 返回值执行A函数,会固定的返回一个Promise对象。得到该对象后便可监设置成功或失败时的回调函数进行监听。如果函数执行顺利并结束,返回的P对象的状态会从等待转变成成功,并输出return命令的返回结果(没有则为undefined)。如果函数执行途中失败,JS会认为A函数已经完成执行,返回的P对象的状态会从等待转变成失败,并输出错误信息。// 成功执行案例A1().then(res => { console.log(‘执行成功’, res); // 10});async function A1() { let n = 1 * 10; return n;}// 失败执行案例A2().catch(err => { console.log(‘执行失败’, err); // i is not defined.});async function A2() { let n = 1 * i; return n;}1.3 await只有在A函数内部才可以使用await命令,存在于A函数内部的普通函数也不行。引擎会统一将await后面的跟随值视为一个Promise,对于不是Promise对象的值会调用Promise.resolve()进行转化。即便此值为一个Error实例,经过转化后,引擎依然视其为一个成功的Promise,其数据为Error的实例。当函数执行到await命令时,会暂停执行并等待其后的Promise结束。如果该P对象最终成功,则会返回成功的返回值,相当将await xxx替换成返回值。如果该P对象最终失败,且错误没有被捕获,引擎会直接停止执行A函数并将其返回对象的状态更改为失败,输出错误信息。最后,A函数中的return x表达式,相当于return await x的简写。// 成功执行案例A1().then(res => { console.log(‘执行成功’, res); // 约两秒后输出100。});async function A1() { let n1 = await 10; let n2 = await new Promise(resolve => { setTimeout(() => { resolve(10); }, 2000); }); return n1 * n2;}// 失败执行案例A2().catch(err => { console.log(‘执行失败’, err); // 约两秒后输出10。});async function A2() { let n1 = await 10; let n2 = await new Promise((resolve, reject) => { setTimeout(() => { reject(10); }, 2000); }); return n1 * n2;}2 入室2.1 继发与并发对于存在于JS语句(for, while等)的await命令,引擎遇到时也会暂停执行。这意味着可以直接使用循环语句处理多个异步。以下是处理继发的两个例子。A函数处理相继发生的异步尤为简洁,整体上与同步代码无异。// 两个方法A1和A2的行为结果相同,都是每隔一秒输出10,输出三次。async function A1() { let n1 = await createPromise(); console.log(‘N1’, n1); let n2 = await createPromise(); console.log(‘N2’, n2); let n3 = await createPromise(); console.log(‘N3’, n3);}async function A2() { for (let i = 0; i< 3; i++) { let n = await createPromise(); console.log(‘N’ + (i + 1), n); }}function createPromise() { return new Promise(resolve => { setTimeout(() => { resolve(10); }, 1000); });}接下来是处理并发的三个例子。A1函数使用了Promise.all生成一个聚合异步,虽然简单但灵活性降低了,只有都成功和失败两种情况。A3函数相对A2仅仅为了说明应该怎样配合数组的遍历方法使用async函数。重点在A2函数的理解上。A2函数使用了循环语句,实际是继发的获取到各个异步值,但在总体的时间上相当并发(这里需要好好理解一番)。因为一开始创建reqs数组时,就已经开始执行了各个异步,之后虽然是逐一继发获取,但总花费时间与遍历顺序无关,恒等于耗时最多的异步所花费的时间(不考虑遍历、执行等其它的时间消耗)。// 三个方法A1, A2和A3的行为结果相同,都是在约一秒后输出[10, 10, 10]。async function A1() { let res = await Promise.all([createPromise(), createPromise(), createPromise()]); console.log(‘Data’, res);}async function A2() { let res = []; let reqs = [createPromise(), createPromise(), createPromise()]; for (let i = 0; i< reqs.length; i++) { res[i] = await reqs[i]; } console.log(‘Data’, res);}async function A3() { let res = []; let reqs = [9, 9, 9].map(async (item) => { let n = await createPromise(item); return n + 1; }); for (let i = 0; i< reqs.length; i++) { res[i] = await reqs[i]; } console.log(‘Data’, res);}function createPromise(n = 10) { return new Promise(resolve => { setTimeout(() => { resolve(n); }, 1000); });}2.2 错误处理一旦await后面的Promise转变成rejected,整个async函数便会终止。然而很多时候我们不希望因为某个异步操作的失败,就终止整个函数,因此需要进行合理错误处理。注意,这里所说的错误不包括引擎解析或执行的错误,仅仅是状态变为rejected的Promise对象。处理的方式有两种:一是先行包装Promise对象,使其始终返回一个成功的Promise。二是使用try.catch捕获错误。// A1和A2都执行成,且返回值为10。A1().then(console.log);A2().then(console.log);async function A1() { let n; n = await createPromise(true); return n;}async function A2() { let n; try { n = await createPromise(false); } catch (e) { n = e; } return n;}function createPromise(needCatch) { let p = new Promise((resolve, reject) => { reject(10); }); return needCatch ? p.catch(err => err) : p;}2.3 实现原理前言中已经提及,A函数是使用G函数进行异步处理的增强版。既然如此,我们就从其改进的方面入手,来看看其基于G函数的实现原理。A函数相对G函数的改进体现在这几个方面:更好的语义,内置执行器和返回值是Promise。更好的语义。G函数通过在function后使用来标识此为G函数,而A函数则是在function前加上async关键字。在G函数中可以使用yield命令暂停执行和交出执行权,而A函数是使用await来等待异步返回结果。很明显,async和await更为语义化。// G函数function request() { let n = yield createPromise();}// A函数async function request() { let n = await createPromise();}function createPromise() { return new Promise(resolve => { setTimeout(() => { resolve(10); }, 1000); });}内置执行器。调用A函数便会一步步自动执行和等待异步操作,直到结束。如果需要使用G函数来自动执行异步操作,需要为其创建一个自执行器。通过自执行器来自动化G函数的执行,其行为与A函数基本相同。可以说,A函数相对G函数最大改进便是内置了自执行器。// 两者都是每隔一秒钟打印出10,重复两次。// A函数A();async function A() { let n1 = await createPromise(); console.log(n1); let n2 = await createPromise(); console.log(n2);}// G函数,使用自执行器执行。spawn(G);function* G() { let n1 = yield createPromise(); console.log(n1); let n2 = yield createPromise(); console.log(n2);}function spawn(genF) { return new Promise(function(resolve, reject) { const gen = genF(); function step(nextF) { let next; try { next = nextF(); } catch(e) { return reject(e); } if(next.done) { return resolve(next.value); } Promise.resolve(next.value).then(function(v) { step(function() { return gen.next(v); }); }, function(e) { step(function() { return gen.throw(e); }); }); } step(function() { return gen.next(undefined); }); });}function createPromise() { return new Promise(resolve => { setTimeout(() => { resolve(10); }, 1000); });}延伸ES6精华:Promise Generator:JS执行权的真实操作者 ...

September 1, 2018 · 4 min · jiezi