摘要:也就是说,你调用生成器函数,它会返回给你一个迭代器。迭代器会遍历每个中断点。等同于总结异步解决方案还有其他的一些方法不过都不重要我们只要掌握了用写异步代码更方便维护第一次写文章写的不好多多包涵毕竟很多东西都是站在前任人的肩膀上直接拿过来的
由于JavaScript是单线程的一门脚本语言(主线程是单线程)
所以异步问题是个让人常头疼的问题
我们来看一下常见的传统解决方案
1.回调函数回调函数是一种最常见 最传统的方式 类似的这种
// node 的文件读取 let fs = require("fs"); fs.readFile("./test1.js","utf8",function(err,data){ console.log(data) })
这样我们可以在回调函数里拿到文件的内容,然而这样有一个问题, 要是我要读取多个文件,每一个读取的文件都要依赖前一个读取文件的内容
比如 我test1.js的内容是test2的路径
那么就要这样写
let fs = require("fs"); fs.readFile("./test1.js","utf8",function(err,data){ fs.readFile(data,"utf8",function(err,data){ console.log(data) }) })
要是100个 1000个文件呢 ? 由于异步调用无法用try{}catch 捕获 万一中间读取失败了一次又该怎么做? 难道每个函数体内都if(err) 一下? 这种方式难以维护 也就是我们常说的回调地狱
2订阅发布模式我现在有这样一种需求 我需要在不同的文件里读取不同的内容, 等多个文件的内容都读取完毕再一起输出
let fs = require("fs"); let result = {}; fs.readFile("./test1.js","utf8",function(err,data){ result.test1 = data fs.readFile("./test2","utf8",function(err,data){ result.test2 = data console.log(result) }) })
用回调方式会带来什么问题? 需求: 这些异步请求没有依赖关系 我需要同时发起 而不是等待上一次读取的结果
现在我们来聊聊 订阅发布模式
订阅发布模式定义了一种一对多的依赖关系,让多个订阅者对象同时监听某一个主题对象。这个主题对象在自身状态变化时,会通知所有订阅者对象,使它们能够自动更新自己的状态。 通俗点就事说, 我把我要操作的事放入一个待执行的队列里, 等达到某一个条件,待执行队列依次执行,那么上代码
let fs = require("fs"); let result = {}; class Publish { constructor() { this.list = [] }; on(fn){ this.list.push(fn) }; emit(string){ alert(string) if (Object.keys(result).length == 2) { this.list.forEach(fn => { fn() }) } } } let p = new Publish() p.on(function () { console.log(result) }) fs.readFile("./test1.js", "utf8", function (err, data) { result.test1 = data p.emit("已经读取到test1的文件") }) fs.readFile("./test2", "utf8", function (err, data) { result.test2 = data p.emit("已经读取到test2的文件") })
原理其实也就是回调函数
问题:发布订阅跟观察者模式有什么区别??
3 Promise好在我们有了Promise这个类 关于Promise的文章有很多 大家自行可以搜索一下
我们来看下Promise A+ 规范
那根据这个规范我们简单的写一遍promise的源码吧
我们来定义2个文件
Promise.js和require.js
//require.js let Promise = require("./promise.js") let p = new Promise((resolve, reject) => { setTimeout(()=>{ resolve(100) },100) }) p.then(function(data){ console.log(data) },function(e){ console.log(e) })
//promise.js class Promise { constructor(executor){ // promise的三个状态 this.state = "pending" this.value = undefined this.reason = undefined // 有可能调用then的时候 并没有resolve或者reject 所以这里用来存放then之后要做的事 this.onResolvedCallbacks = [] this.onRejectedCallbacks = [] const resolve = (value) => { // 我们需要判断resolve出来的值是否还是一个promise if(value instanceof Promise){ return value.then(resolve,reject) } // promiseA+ 规范要求这么写 setTimeout(()=>{ if (this.state === "pending") { this.state = "resolved" this.value = value // 把保存起来的函数一一执行然后结果传给下一个 this.onResolvedCallbacks.forEach( fn => { return fn(value) }) } }) } const reject = (reason) => { setTimeout(()=>{ if (this.state === "pending") { this.state = "rejected" this.reason = reason this.onRejectedCallbacks.forEach(fn => { return fn(reason) }) } }) } try { executor(resolve,reject) } catch (error) { reject(error) } } then(onFulfilled,onRejected){ // new 的时候马上执行executor ----> 就是(resolve,reject)=>{ }() 拿到resolve跟reject 然后做状态判断该调用哪个 onFulfilled = typeof onFulfilled == "function" ? onFulfilled : function (value) { return value }; onRejected = typeof onRejected == "function" ? onRejected : function (value) { throw value }; let promise2 = new Promise((resolve,reject)=>{ if (this.state === "resolved"){ //onFulfilled or onRejected must not be called until the execution context stack contains only platform code. [3.1]. // 规范上要这么做 防止直接resolve 同步调用then 这个时候promise2不存在报错 // 执行顺序参考浏览器事件环 哪天有空多带带写一篇 setTimeout(()=>{ // 因为onFulfilled都是异步调用 所以不能在new Promise的时候捕获到 try { let x = onFulfilled(this.value) // then成功的回调 resolvePromise(promise2, x, resolve, reject) } catch (error) { reject(error) } }) } if (this.state === "rejected"){ setTimeout(() => { try { let x = onRejected(this.reason) // 失败的回调 resolvePromise(promise2, x, resolve, reject) } catch (error) { reject(error) } }) } if (this.state === "pending"){ // 如果executor是个异步方法 那么会先调用then 所以这里把成功回调跟失败的回调都存起来 this.onResolvedCallbacks.push((value)=>{ try { let x = onFulfilled(value) resolvePromise(promise2, x, resolve, reject) } catch (error) { console.log(error) reject(error) } }) this.onRejectedCallbacks.push((reason)=>{ try { let x = onRejected(reason) resolvePromise(promise2, x, resolve, reject) } catch (error) { reject(error) } }) } }) // then返回一个promise return promise2 } catch(onRejected){ return this.then(null, onRejected); } static all(promises){ return new Promise(function (resolve, reject) { let result = []; let count = 0; for (let i = 0; i < promises.length; i++) { promises[i].then(function (data) { result[i] = data; if (++count == promises.length) { resolve(result); } }, function (err) { reject(err); }); } }); } } const resolvePromise = (promise2, x, resolve, reject)=>{ // promise2 跟 then的成功回调返回值有可能是同一个值 if(promise2 === x){ return reject(new TypeError("报错 循环引用了")) } let then,called; // 要么对象要么函数 if(x !== null&&((typeof x === "object" || typeof x === "function")) ){ try { then = x.then // 有可能是getter定义的会报错 // then 有可能是个函数或者普通值 if(typeof then === "function"){ // 如果then是个函数的话 就认为它是个promise then.call(x,function(){ if(called) return called = true resolvePromise(promise2, y, resolve, reject); },function(error){ if (called) return called = true reject(error) }) }else{ resolve(x) } } catch (error) { if (called) return called = true reject(error) } }else{ // x是个普通值 resolve(x) } } module.exports = Promise
执行require.js的结果是
这样我们就实现了一个promise 是不是很棒棒? 现在我们可以promise.then 链式调用了 然后用catch做统一错误处理 解决了上面错误捕获的问题 还有没有更好的方法? 当然有!
4 生成器 迭代器这篇文章讲的比较详细:迭代器
在讲async await 之前 我们先讲一下 生成器
**当你在执行一个函数的时候,你可以在某个点暂停函数的执行,并且做一些其他工作,然后再返回这个函数继续执行, 甚至是携带一些新的值,然后继续执行。
上面描述的场景正是JavaScript生成器函数所致力于解决的问题。当我们调用一个生成器函数的时候,它并不会立即执行, 而是需要我们手动的去执行迭代操作(next方法)。也就是说,你调用生成器函数,它会返回给你一个迭代器。迭代器会遍历每个中断点。
next 方法返回值的 value 属性,是 Generator 函数向外输出数据;next 方法还可以接受参数
function* foo () { var index = 0; while (index < 2) { yield index++; //暂停函数执行,并执行yield后的操作 } } var bar = foo(); // 返回的其实是一个迭代器 console.log(bar.next()); // { value: 0, done: false } console.log(bar.next()); // { value: 1, done: false } console.log(bar.next()); // { value: undefined, done: true }
Generator函数的标志就是function关键词后连缀一个"*" 配合yield 暂停函数 返回的是一个迭代器 每次执行next的时候 停在yield
我们都见过类数组结构吧
let likeArray = { 0: 1, 1: 2, 2: 3, length: 3 } let arr = [...likeArray] //执行这段代码会报错 报错信息likeArray is not iterable likeArray是不可枚举的 那么我们如果想实现这样的类数组转为数组 怎么办呢
我们先看一下函数里的argument跟类数组有什么区别
function(){ console.log(argument)}
我们改下一下类数组结构
let likeArray = { 0: 1, 1: 2, 2: 3, length: 3, [Symbol.iterator](){ return { next() { return { value: 1, done: false } } } } } //在执行 let arr = [...likeArray] 控制台报错FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory 感觉是不是有点像那么回事了 我们再改写 let likeArray = { 0: 1, 1: 2, 2: 3, length: 3, [Symbol.iterator](){ let index = 0 let self = this return { next() { return { done: self.length === index value: self[index++], } } } } } // 输出[1,2,3] 只有在done是false的时候表示迭代完成 就不再继续执行了 value是每次迭代返回的值 再改写一下 let likeArray = { 0: 1, 1: 2, 2: 3, length: 3, [Symbol.iterator]:function*() { let index = 0; while (index !== this.length) { yield this[index++] } } } console.log([...likeArray]) //[1,2,3] 调用返回一个迭代器 ... 每次调用迭代器的next方法 返回{value,done}
生成器可以配合node.js中的co, 借助于Promise,你可以使用更加优雅的方式编写非阻塞代码。
例子:
let fs = require("fs"); function readFile(filename) { return new Promise(function (resolve, reject) { fs.readFile(filename, function (err, data) { if (err) reject(err); else resolve(data); }) }) } function *read() { let template = yield readFile("./template.txt"); let data = yield readFile("./data.txt"); return template + "+" + data; } co(read).then(function (data) { console.log(data); }, function (err) { console.log(err); });5 async/await
有了上面的基础 async/await 更加容易明白了
async/await的优点有
1.内置执行器
2.更好的语义
3.更广的适用性
let fs = require("fs"); function readFile(filename) { return new Promise(function (resolve, reject) { fs.readFile(filename, "utf8", function (err, data) { if (err) reject(err); else resolve(data); }) }) } async function read() { let template = await readFile("./template.txt"); let data = await readFile("./data.txt"); return template + "+" + data; } let result = read(); result.then(data=>console.log(data));
可以直接await 一个promise 使得异步代码执行看起来像同步一样 更优雅
async 函数的实现,就是将 Generator 函数和自动执行器,包装在一个函数里。
async function read() { let template = await readFile("./template.txt"); let data = await readFile("./data.txt"); return template + "+" + data; } // 等同于 function read(){ return co(function*() { let template = yield readFile("./template.txt"); let data = yield readFile("./data.txt"); return template + "+" + data; }); }
**总结: 异步解决方案还有其他的一些方法 不过都不重要 我们只要掌握了async/await 用async/await写异步代码 更方便维护
第一次写文章 写的不好多多包涵 毕竟很多东西都是站在前任人的肩膀上直接拿过来的**
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/99566.html
摘要:的翻译文档由的维护很多人说,阮老师已经有一本关于的书了入门,觉得看看这本书就足够了。前端的异步解决方案之和异步编程模式在前端开发过程中,显得越来越重要。为了让编程更美好,我们就需要引入来降低异步编程的复杂性。 JavaScript Promise 迷你书(中文版) 超详细介绍promise的gitbook,看完再不会promise...... 本书的目的是以目前还在制定中的ECMASc...
摘要:从最开始的到封装后的都在试图解决异步编程过程中的问题。为了让编程更美好,我们就需要引入来降低异步编程的复杂性。写一个符合规范并可配合使用的写一个符合规范并可配合使用的理解的工作原理采用回调函数来处理异步编程。 JavaScript怎么使用循环代替(异步)递归 问题描述 在开发过程中,遇到一个需求:在系统初始化时通过http获取一个第三方服务器端的列表,第三方服务器提供了一个接口,可通过...
摘要:异步编程解决方案笔记最近读了朴灵老师的深入浅出中异步编程一章,并参考了一些有趣的文章。另外回调函数中的也失去了意义,这会使我们的程序必须依赖于副作用。 JavaScript 异步编程解决方案笔记 最近读了朴灵老师的《深入浅出NodeJS》中《异步编程》一章,并参考了一些有趣的文章。在此做个笔记,记录并巩固学到的知识。 JavaScript异步编程的两个核心难点 异步I/O、事件驱动使得...
摘要:异步问题回调地狱首先,我们来看下异步编程中最常见的一种问题,便是回调地狱。同时使用也是异步编程最基础和核心的一种解决思路。基于,目前也被广泛运用,其是异步编程的一种解决方案,比传统的回调函数解决方案更合理和强大。 关于 微信公众号:前端呼啦圈(Love-FED) 我的博客:劳卜的博客 知乎专栏:前端呼啦圈 前言 在实际编码中,我们经常会遇到Javascript代码异步执行的场景...
摘要:最受欢迎的引擎是,在和中使用,用于,以及所使用的。怎么处理每个引擎都有一个基本组件,称为调用栈。也就是说,如果有其他函数等待执行,函数是不能离开调用栈的。每个异步函数在被送入调用栈之前必须通过回调队列。例如方法是在中传递的回调函数。 翻译:疯狂的技术宅 原文:www.valentinog.com/blog/engine… 从Call Stack,Global Me...
摘要:最受欢迎的引擎是,在和中使用,用于,以及所使用的。单线程的我们说是单线程的,因为有一个调用栈处理我们的函数。也就是说,如果有其他函数等待执行,函数是不能离开调用栈的。每个异步函数在被送入调用栈之前必须通过回调队列。 翻译:疯狂的技术宅原文:https://www.valentinog.com/bl... 本文首发微信公众号:前端先锋欢迎关注,每天都给你推送新鲜的前端技术文章 sh...
阅读 1566·2021-09-26 09:46
阅读 2637·2021-09-07 09:59
阅读 2733·2021-09-07 09:59
阅读 1829·2019-08-30 14:20
阅读 906·2019-08-26 13:39
阅读 3120·2019-08-26 12:24
阅读 752·2019-08-26 11:55
阅读 1201·2019-08-23 16:49