做网站产品资料表格,网站托管服务提供商,点点 wordpress,wordpress上传空间后同步异步的概念
在讲这四种异步方案之前#xff0c;我们先来明确一下同步和异步的概念#xff1a;
所谓同步(synchronization)#xff0c;简单来说#xff0c;就是顺序执行#xff0c;指的是同一时间只能做一件事情#xff0c;只有目前正在执行的事情做完之后异步的概念
在讲这四种异步方案之前我们先来明确一下同步和异步的概念
所谓同步(synchronization)简单来说就是顺序执行指的是同一时间只能做一件事情只有目前正在执行的事情做完之后才能做下一件事情。 比如咱们去火车站买票假设窗口只有1个那么同一时间只能处理1个人的购票业务其余的需要进行排队。这种one by one的动作就是同步。 同步操作的优点在于做任何事情都是依次执行井然有序不会存在大家同时抢一个资源的问题。 同步操作的缺点在于会阻塞后续代码的执行。如果当前执行的任务需要花费很长的时间那么后面的程序就只能一直等待。从而影响效率对应到前端页面的展示来说有可能会造成页面渲染的阻塞大大影响用户体验。
所谓异步(Asynchronization)指的是当前代码的执行不影响后面代码的执行。当程序运行到异步的代码时会将该异步的代码作为任务放进任务队列而不是推入主线程的调用栈。等主线程执行完之后再去任务队列里执行对应的任务即可。 因此异步操作的优点就是不会阻塞后续代码的执行。
js中异步的应用场景
开篇讲了同步和异步的概念那么在JS中异步的应用场景有哪些呢
定时任务setTimeout、setInterval网络请求ajax请求、动态创建img标签的加载事件监听器addEventListener
ajax请求Ajax即Asynchronous Javascript And XML异步JavaScript和XML在 2005年被Jesse James Garrett提出的新术语用来描述一种使用现有技术集合的‘新’方法包括HTML 或 XHTML, CSS, JavaScript, DOM, XML, XSLT, 以及最重要的XMLHttpRequest。
实现异步的四种方法
对于setTimeout、setInterval、addEventListener这种异步场景不需要我们手动实现异步直接调用即可。 但是对于ajax请求、node.js中操作数据库这种异步就需要我们自己来实现了~ 回调函数
在微任务队列出现之前JS实现异步的主要方式就是通过回调函数。 以一个简易版的Ajax请求为例我们在业务代码里可以这样调用ajax请求
ajax({url:#,type:GET,success:function(e){// 回调函数里就是对请求结果的处理}
});
ajax的success方法就是一个回调函数回调函数中执行的是我们请求成功之后要做的进一步操作。 这样就初步实现了异步但是回调函数有一个非常严重的缺点那就是回调地狱的问题。 大家可以试想一下如果我们在回调函数里再发起一个ajax请求呢那岂不是要在success函数里继续写一个ajax请求那如果需要多级嵌套发起ajax请求呢岂不是需要多级嵌套如果嵌套的层级很深的话我们的代码结构可能就会变成这样 因此为了解决回调地狱的问题提出了Promise、async/await、generator的概念。
Promise
Promise作为典型的微任务之一它的出现可以使JS达到异步执行的效果。 一个Promise函数的结构如下列代码如下
const promise new Promise((resolve, reject) {resolve(a);
});
promise.then((arg) { console.log(执行resolve,参数是${arg}) }).catch((arg) { console.log(执行reject,参数是${arg}) }).finally(() { console.log(结束promise) });
如果我们需要嵌套执行异步代码相比于回调函数来说Promise的执行方式如下列代码所示
const promise new Promise((resolve, reject) {resolve(1);
});
promise.then((value) {console.log(value);return value * 2;}).then((value) {console.log(value);return value * 2;}).then((value) {console.log(value);}).catch((err) {console.log(err);});
即通过then来实现多级嵌套(链式调用)这看起来是不是就比回调函数舒服多了~
每个Promise都会经历的生命周期是
进行中pending - 此时代码执行尚未结束所以也叫未处理的unsettled已处理settled - 异步代码已执行结束 已处理的代码会进入两种状态中的一种 已完成fulfilled - 表明异步代码执行成功由resolve()触发已拒绝rejected- 遇到错误异步代码执行失败 由reject()触发
因此pendingfulfilled, rejected就是Promise中的三种状态啦~ 大家一定要牢记在Promise中要么包含resolve()来表示Promise的状态为fulfilled,要么包含reject()来表示Promise的状态为rejected。 不然我们的Promise就会一直处于pending的状态直至程序崩溃...
除此之外Promise不仅很好的解决了链式调用的问题它还有很多神奇的操作呢
Promise.all(promises)接收一个包含多个Promise对象的数组等待所有都完成时返回存放它们结果的数组。如果任一被拒绝则立即抛出错误其他已完成的结果会被忽略Promise.allSettled(promises): 接收一个包含多个Promise对象的数组等待所有都已完成或者已拒绝时返回存放它们结果对象的数组。每个结果对象的结构为{status:fulfilled // 或 rejected, value // 或reason}Promise.race(promises): 接收一个包含多个Promise对象的数组等待第一个有结果完成/拒绝的Promise并把其result/error作为结果返回
function getPromises(){return [new Promise(((resolve, reject) setTimeout(() resolve(1), 1000))),new Promise(((resolve, reject) setTimeout(() reject(new Error(2)), 2000))),new Promise(((resolve, reject) setTimeout(() resolve(3), 3000))),];
}Promise.all(getPromises()).then(console.log);
Promise.allSettled(getPromises()).then(console.log);
Promise.race(getPromises()).then(console.log); 打印结果如下 Generator
Generator是ES6提出的一种异步编程的方案。因为手动创建一个iterator十分麻烦因此ES6推出了generator用于更方便的创建iterator。也就是说Generator就是一个返回值为iterator对象的函数。
在讲Generator之前我们先来看看iterator是什么
iterator是什么
iterator中文名叫迭代器。它为js中各种不同的数据结构(Object、Array、Set、Map)提供统一的访问机制。任何数据结构只要部署了Iterator接口就可以完成遍历操作。 因此iterator也是一种对象不过相比于普通对象来说它有着专为迭代而设计的接口。
iterator 的作用
为各种数据结构提供一个统一的、简便的访问接口使得数据结构的成员能够按某种次序排列ES6 创造了一种新的遍历命令for…of循环Iterator 接口主要供for…of消费
iterator的结构 它有next方法该方法返回一个包含value和done两个属性的对象我们假设叫result.
value是迭代的值后者是表明迭代是否完成的标志。true表示迭代完成false表示没有。iterator内部有指向迭代位置的指针每次调用next自动移动指针并返回相应的result
原生具备iterator接口的数据结构如下
ArrayMapSetStringTypedArray函数里的arguments对象NodeList对象
这些数据结构都有一个Symbol.iterator属性可以直接通过这个属性来直接创建一个迭代器。 也就是说Symbol.iterator属性只是一个用来创建迭代器的接口而不是一个迭代器因为它不含遍历的部分。
使用Symbol.iterator接口生成iterator迭代器来遍历数组的过程为
let arr [a,b,c];let iter arr[Symbol.iterator]();iter.next() // { value: a, done: false }
iter.next() // { value: b, done: false }
iter.next() // { value: c, done: false }
iter.next() // { value: undefined, done: true }
for ... of的循环内部实现机制其实就是iterator它首先调用被遍历集合对象的 Symbol.iterator 方法该方法返回一个迭代器对象迭代器对象是可以拥有.next()方法的任何对象然后在 for ... of 的每次循环中都将调用该迭代器对象上的 .next 方法。然后使用for i of打印出来的i也就是调用.next方法后得到的对象上的value属性。
对于原生不具备iterator接口的数据结构比如Object我们可以采用自定义的方式来创建一个遍历器。
比如我们可以自定义一个iterator来遍历对象
let obj {a: hello, b: world};
// 自定义迭代器
function createIterator(items) {let keyArr Object.keys(items);let i 0;return {next: function () {let done (i keyArr.length);let value !done ? items[keyArr[i]] : undefined;return {value: value,done: done,};}};
}let iterator createIterator(obj);
console.log(iterator.next()); // { value: hello, done: false }
console.log(iterator.next()); // { value: world, done: false }
console.log(iterator.next()); // { value: undefined, done: true }
接下来我们来聊聊Generator:
我们通过一个例子来看看Gnerator的特征
function* createIterator() {yield 1;yield 2;yield 3;
}
// generators可以像正常函数一样被调用不同的是会返回一个 iterator
let iterator createIterator();
console.log(iterator.next().value); // 1
console.log(iterator.next().value); // 2
console.log(iterator.next().value); // 3
Generator 函数是 ES6 提供的一种异步编程解决方案。形式上Generator 函数是一个普通函数但是有两个特征:
function关键字与函数名之间有一个星号函数体内部使用yield语句定义不同的内部状态
Generator函数的调用方法与普通函数一样也是在函数名后面加上一对圆括号。不同的是调用Generator函数后该函数并不执行返回的也不是函数运行结果而是一个指向内部状态的指针对象也就是遍历器对象Iterator Object 发现generator函数的返回值的原型链上确实有iterator对象该有的next这充分说明了generator的返回值是一个iterator。除此之外还有函数该有的return方法和throw方法。
在普通函数中我们想要一个函数最终的执行结果一般都是return出来或者以return作为结束函数的标准。运行函数时也不能被打断期间也不能从外部再传入值到函数体内。 但在generator中就打破了这几点所以generator和普通的函数完全不同。 当以function*的方式声明了一个Generator生成器时内部是可以有许多状态的以yield进行断点间隔。期间我们执行调用这个生成的Generator,他会返回一个遍历器对象用这个对象上的方法实现获得一个yield后面输出的结果。
function* generator() {yield 1yield 2
};
let iterator generator();
iterator.next() // {value: 1, done: false}
iterator.next() // {value: 2, done: false}
iterator.next() // {value: undefined, done: true}
yield和return的区别 都能返回紧跟在语句后面的那个表达式的值yield相比于return来说更像是一个断点。遇到yield函数暂停执行下一次再从该位置继续向后执行而return语句不具备位置记忆的功能。一个函数里面只能执行一个return语句但是可以执行多次yield表达式。正常函数只能返回一个值因为只能执行一次returnGenerator 函数可以返回一系列的值因为可以有任意多个yield
语法注意点
yield表达式只能用在 Generator 函数里面yield表达式如果用在另一个表达式之中必须放在圆括号里面yield表达式用作函数参数或放在赋值表达式的右边可以不加括号。如果 return 语句后面还有 yield 表达式那么后面的 yield 完全不生效
使用Generator的其余注意事项
需要注意的是yield 不能跨函数。并且yield需要和*配套使用别处使用无效
function* createIterator(items) {items.forEach(function (item) {// 语法错误yield item 1;});
}
箭头函数不能用做 generator
讲了这么多那么Generator到底有什么用呢
因为Generator可以在执行过程中多次返回所以它看上去就像一个可以记住执行状态的函数利用这一点写一个generator就可以实现需要用面向对象才能实现的功能。Generator还有另一个巨大的好处就是把异步回调代码变成“同步”代码。这个在ajax请求中很有用避免了回调地狱.
async/await
async/await是ES7提出的关于异步的终极解决方案。我看网上关于async/await是谁的语法糖这块有两个版本
第一个版本说async/await是Generator的语法糖第二个版本说async/await是Promise的语法糖
其实这两种说法都没有错。
关于async/await是Generator的语法糖 所谓Generator语法糖表明的就是aysnc/await实现的就是generator实现的功能。但是async/await比generator要好用。因为generator执行yield设下的断点采用的方式就是不断的调用iterator方法这是个手动调用的过程。针对generator的这个缺点后面提出了co这个库函数来自动执行next相比于之前的方案这种方式确实有了进步但是仍然麻烦。而async配合await得到的就是断点执行后的结果。因此async/await比generator使用更普遍。
总结下来async函数对 Generator函数的改进主要体现在以下三点:
内置执行器Generator函数的执行必须靠执行器因为不能一次性执行完成所以之后才有了开源的 co函数库。但是async函数和正常的函数一样执行也不用 co函数库也不用使用 next方法而 async函数自带执行器会自动执行。适用性更好co函数库有条件约束yield命令后面只能是 Thunk函数或 Promise对象但是 async函数的 await关键词后面可以不受约束。可读性更好async和 await比起使用 *号和 yield语义更清晰明了。
关于async/await是Promise的语法糖 如果不使用async/await的话Promise就需要通过链式调用来依次执行then之后的代码
function counter(n){return new Promise((resolve, reject) { resolve(n 1);});
}function adder(a, b){return new Promise((resolve, reject) { resolve(a b);});
}function delay(a){return new Promise((resolve, reject) { setTimeout(() resolve(a), 1000);});
}
// 链式调用写法
function callAll(){counter(1).then((val) adder(val, 3)).then((val) delay(val)).then(console.log);
}
callAll();//5
虽然相比于回调地狱来说链式调用确实顺眼多了。但是其呈现仍然略繁琐了一些。 而async/await的出现就使得我们可以通过同步代码来达到异步的效果
async function callAll(){const count await counter(1);const sum await adder(count, 3);console.log(await delay(sum));
}
callAll();// 5
由此可见Promise搭配async/await的使用才是正解因为通常await关键字后面都是跟一个Promise这个promise状态变为fulfilled才会执行await后续的代码所以await后面的代码相当于包括在.then方法的回调中如果状态变为rejected你则需要在函数内部try catch或者进行链式调用进行.catch操作。
总结
promise让异步执行看起来更清晰明了通过then让异步执行结果分离出来。async/await其实是基于Promise的。async函数其实是把promise包装了一下。使用async函数可以让代码简洁很多不需要promise一样需要些then不需要写匿名函数处理promise的resolve值也不需要定义多余的data变量还避免了嵌套代码。async函数是Generator函数的语法糖。async函数的返回值是 promise 对象这比 Generator 函数的返回值是 Iterator 对象方便多了。同时我们还可以用await来替代then方法指定下一步的操作。感觉Promiseasync的操作最为常见。因为Generator的常用功能可以直接由async来体现呀