JS的异步相关知识
js里面一共有以下异步的解决方案
传统的回调
省略 。。。。
生成器
Generator 函数是 ES6 提供的一种异步编程解决方案, 语法上,首先可以把它理解成,Generator 函数是一个状态机,封装了多个内部状态。执行 Generator 函数会返回一个遍历器对象,也就是说,Generator 函数除了状态机,还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。
怎么定义一个生成器
function* gen1(){
yield 1;
yield 2;
}
function* test() {
yield 'a';
}
test.next(); // value: "a", done: false}
调用 Generator 函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象,也就是遍历器对象(Iterator Object)。
每次调用遍历器对象的next方法,就会返回一个有着value和done两个属性的对象。
value属性表示当前的内部状态的值,是yield表达式后面那个表达式的值;
done属性是一个布尔值,表示是否遍历结束
function*
在 JavaScript 中不是 函数指针,而是一种特殊的函数语法,叫做生成器函数(Generator Function)。
function*
是什么?
function*
用于定义一个生成器函数 。生成器函数是一种可以暂停和恢复执行 的特殊函数。它返回一个生成器对象(Generator Object) ,这个对象实现了迭代器(Iterator)协议。
Generator 函数就是一个封装的异步任务,或者说是异步任务的容器。
异步操作需要暂停的地方,都用 yield 语句注明
调用 Generator 函数,返回的就是一个生成器(迭代器)对象。
然后调用迭代器对象的 next() 方法,就是按顺序执行代码,知道碰到下一个yield 关键字 ,然后返回,然后要可以执行next() 直到函数体内部流程执行完毕。
next 方法的作用是分阶段执行 Generator 函数。每次调用 next 方法,会返回一个对象,表示当前阶段的信息( value 属性和 done 属性)。value 属性是 yield 语句后面表达式的值,表示当前阶段的值;done 属性是一个布尔值,表示 Generator 函数是否执行完毕,即是否还有下一个阶段。
如:
const gen1 = myGenerator(); // 返回一个生成器对象
result=gen1.next()
console.log(result.value,result.done); //如果没有执行完成的话 done是false
了解generator用法
function* Generator() {
yield "1";
yield Promise.resolve(2);
return "3";
}
var gen = Generator();
console.log(gen); //返回一个指针对象 Generator {<suspended>}
调用next方法
let res1 = gen.next();
console.log(res1); // 返回当前阶段的值 { value: '1', done: false }
let res2 = gen.next();
console.log(res2); // 返回当前阶段的值 { value: Promise { 2 }, done: false }
res2.value.then((res) => {
console.log(res); // 2
});
let res3 = gen.next();
console.log(res3); // { value: '3', done: true }
let res4 = gen.next();
console.log(res4); // { value: undefined, done: true }
async/await实现
而async/await 是**Generator+Promise方案的语法糖。**async 函数的实现,就是将 Generator 函数和自动执行器(自动调用生成器的next()方法,直到最后一个),包装在一个函数里。
generator 函数需要通过调用next()方法,才能往后执行到下一个yield,但是 async 函数却不需要,它能够自动向后执行。
await 可以理解为是 async wait 的简写。await 必须出现在 async 函数内部,不能单独使用
await 字面上使得 JavaScript 等待,直到 promise 处理完成,然后将结果继续下去。
await不能工作在顶级作用域,需要将await代码包裹在一个async函数中
也可以形象的理解: 当执行异步函数时,碰到await时就把这个await 后面的所有代码全部封装成一个promise对象,然后放在微任务队列中等待调用,这样也就类似await后面的代码都必须等待
await后面的一行代码执行完毕。依次这样处理异步函数中的其他代码。
async/await的理解
- async 函数的实现原理,就是将 Generator 函数和自动执行器,包装在一个函数里
- async 函数执行结果返回的是一个Promise
- async 函数就是将 Generator 函数的星号(*)替换成 async,将 yield 替换成await
- async/await 就是 Generator 的语法糖,其核心逻辑是迭代执行next函数
async 函数的实现1
async 函数的实现,就是将 Generator 函数和自动执行器,包装在一个函数里
//第一种
async function fn(args){
// ...
}
//第二种 等同于 一
function fn(args){
return spawn(function*() {
// ...
});
}
所有的 async 函数都可以写成上面的第二种形式,其中的 spawn 函数就是自动执行器。
下面给出 spawn 函数的实现,基本就是前文自动执行器的翻版。
function spawn(genF) {
return new Promise(function(resolve, reject) {
var gen = genF(); //执行器
function step(nextF) {
try {
var 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); });
});
}
综合上面的代码的完整实例
<html>
<head>
<title>Test</title>
</head>
<body>
<div id="container"></div>
</body>
<script>
function testfun(){
console.log('testfun')
}
function fn(args){
return spawn(function*() {
yield testfun();
yield testfun();
});
}
function spawn(genF) {
return new Promise(function(resolve, reject) {
var gen = genF(); //执行器
function step(nextF) {
try {
var next = nextF();
} catch(e) {
return reject(e);
}
if(next.done) {
//如果没有其他迭代了 就把最开始的Promise对象的状态设置为ffulfilled,然后返回结果
//resolve 返回结果会传递给then方法中的onFulfilled函数,也就是这里的next.value
return resolve(next.value);
}
//当前迭代器 没有执行完毕 就继续抛出一个Promise对象,然后继续执行迭代器中的下一个yield表达式
//直到所有的yield表达式都执行完毕
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); });
});
}
fn()
</script>
</html>
其他实例分析
// Generator 函数,依次读取两个文件。
const fs = require('fs');
const readFile = function (fileName) {
return new Promise(function (resolve, reject) {
fs.readFile(fileName, function(error, data) {
if (error) return reject(error);
resolve(data);
});
});
};
const gen = function* () {
const f1 = yield readFile('/etc/fstab');
const f2 = yield readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
==================================
写成async函数,就是下面这样。
const asyncReadFile = async function () {
const f1 = await readFile('/etc/fstab');
const f2 = await readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
一比较就会发现,async函数就是将 Generator 函数的星号(*)替换成async,将yield替换成await,仅此而已。
async函数对 Generator 函数的改进
-
当一个 async 函数中有多个 await命令时,如果不想因为一个出错而导致其与的都无法执行,应将await放在try...catch语句中执行
async function testAwait () {
try {
await func1()
await func2()
await func3()
} catch (error) {
console.log(error)
}
} -
并发执行 await 命令: await 是顺序执行的,Promise.all() 是并行的
// 方法 1
let [res1, res2] = await Promise.all([func1(), func2()])// 方法 2
let func1Promise = func1()
let func2Promise = func2()
let res1 = await func1Promise
let res2 = await func2Promise -
async方法: 一个class方法同样能够使用async,只需要将async放在它之前就可以
就像这样:
class Waiter {
async wait () {
return await Promise.resolve(1)
}
}
new Waiter().wait().then(alert) // 1
=======================
# Class methods:
class Storage {
constructor() {
this.cachePromise = caches.open('avatars');
}
async getAvatar(name) {
const cache = await this.cachePromise;
return cache.match(`/avatars/${name}.jpg`);
}
}
const storage = new Storage();
storage.getAvatar('jaffathecake').then(...);
-
sleep(): 让线程休眠一段时间
// wait ms milliseconds
function wait(ms) {
return new Promise(r => setTimeout(r, ms));
}async function hello() {
await wait(500);
return 'world';
}
业务场景举例
你有三个请求需要发生,第三个请求是依赖于第二个请求的解构第二个请求依赖于第一个请求的结果。若用 ES5实现会有3层的回调,若用Promise 实现至少需要3个then。一个是代码横向发展,另一个是纵向发展。给出 async-await 的实现
//我们仍然使用 setTimeout 来模拟异步请求
function sleep(second, param) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(param);
}, second);
})
}
async function test() {
let result1 = await sleep(2000, 'req01');
let result2 = await sleep(1000, 'req02' + result1);
let result3 = await sleep(500, 'req03' + result2);
console.log(`
${result3}
${result2}
${result1}
`);
}
test();
Promise的基本原理
介绍
Promise 是异步编程的一种解决方案,比传统的解决方案------回调函数和事件------更合理和更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise
对象。Promise的异步在js中是通过微任务的方式构建的,Promise的.then() 会触发生成一个微任务,放在队列中。
所谓Promise
,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。
- 首先在
new Promise
时,需要传入一个回调函数executor函数,Promise的主要业务流程都在executor函数中执行。 - 如果运行在excutor函数中的业务执行成功了,会调用resolve(手动调用)函数;如果执行失败了,则调用reject函数。
- Promise的状态不可逆,同时调用resolve函数和reject函数,默认会采取第一次调用的结果。
- 它有
constructor
构造函数,还有catch
、finally
、then
三个方法
- 首先我们在调用Promise时,会返回一个Promise对象。
Promise/A+的规范比较多,在这列出一下核心的规范。Promise/A+规范
- promise有三个状态:pending,fulfilled,rejected,默认状态是pending。
- promise有一个value保存成功状态的值,有一个reason保存失败状态的值,可以是undefined/thenable/promise。
- promise只能从pending到rejected, 或者从pending到fulfilled,状态一旦确认,就不会再改变。
- promise 必须有一个then方法,then接收两个参数,分别是promise成功的回调onFulfilled, 和promise失败的回调onRejected。
- 如果then中抛出了异常,那么就会把这个异常作为参数,传递给下一个then的失败的回调onRejected。
知识点1
new Promise((resolve, reject) => {
console.log("tst111")
})
///Promise 对象创建的时候 传入的参数 函数 会被同步执行,就是说在当前宏任务同步执行。
知识点2
const resolvePromise = Promise.resolve(1); //立刻返回一个fulled状态的Promise
//执行then 产生一个微任务
resolvePromise.then((res) => console.log(res));
console.log('test');
先输出 test
然后是1
其他学习记录:
https://blog.csdn.net/binqian/article/details/145742026?spm=1011.2415.3001.5331
https://blog.csdn.net/binqian/article/details/145737399?spm=1011.2415.3001.5331
https://blog.csdn.net/binqian/article/details/145658887?spm=1011.2415.3001.5331