JavaScript它的执行环境是单线程的,单线程就是任务只能一个一个的完成,这个任务完成之后才能执行下一个,它会阻塞其它任务。
而异步模式可以一起执行多个任务。常见的异步模式有定时器,接口调用和事件函数,Promise就是接口调用里面的一种方式,它是es6提供的一种异步解决方案。简单来说的话,Promis就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作的结果)。
JavaScript中常见的接口调用有
Ajex,fetch,promise,axios,async/await。
多次异步调用的依赖分析:
1.多次异步调用的结果,顺序可能不同步。
2.异步调用的结果如果存在依赖,则需要嵌套。
3.在ES5中,当进行多层嵌套回调时,会导致代码层次过多,很难进行维护和二次开发;而且会导致回调地狱的问题。ES6中的Promise 就可以解决这两个问题。
Promise介绍
Promis是异步编程的一种方案。从语法上来讲,Promise是一个对象,它可以获取异步操作的消息。
Promise的优点:
Promise对象,可以将异步操作以同步的流程表达出来,可以很好地解决回调的问题,避免层层嵌套的回调函数。语法非常简洁,Promise对象提供了简洁的API,使得控制异步操作更加容易。
Promise的基本用法
Es6规定Promise对象是一个构造函数,用来生成Promise实例
(1)使用new实例化一个Promise对象,Promise的构造函数中传递一个参数。这个参数是一个函数,改函数用来处理异步任务。
(2)并且会传入两个参数:reslove和reject,分别表示异步执行成功后的回调函数和异步执行失败后的回调函数;
(3)通过Promis.then()处理返回结果
在浏览器控制台上的输出如下
这里用timeout方法返回一个Promise实例,表示一段时间以后才会发生的结果。过了指定的时间(100ms)以后,Promise实例的状态变为resolved,就会触发then方法绑定的回调函数。
这里要知道Promise对象的三个状态
初始化状态(等待状态):pending
成功状态:fullfilled
失败状态:rejected
(1)当new Promise()执行之后,promise对象的状态会被初始化为pending,这个状态是初始化状态。new Promise()这行代码,括号里的内容是同步执行的。括号里定义一个function,function有两个参数:resolve和reject。如下:
如果请求成功了,则执行resolve(),此时,promise的状态会被自动修改为fullfilled。
如果请求失败了,则执行reject(),此时,promise的状态会被自动修改为rejected
(2)promise.then()方法,括号里面有两个参数,分别代表两个函数 function1 和 function2:
如果promise的状态为fullfilled(意思是:如果请求成功),则执行function1里的内容
如果promise的状态为rejected(意思是,如果请求失败),则执行function2里的内容
这里有一段代码
let promise = new Promise(function(resolve, reject) {
console.log('Promise');
resolve();
});
promise.then(function() {
console.log('resolved.');
});
console.log('Hi!');
输出顺序是Promise,Hi!,resolved
上面代码中,Promise 新建后立即执行,所以首先输出的是Promise。然后,then方法指定的回调函数,将在当前脚本所有同步任务执行完才会执行,所以resolved最后输出。
这里简单说一下JavaScript中的宏任务和微任务
宏任务有Event Table、Event Queue,微任务有Event Queue
1.宏任务:包括整体代码script,setTimeout,setInterval、I/O、UI 交互事件、setImmediate(Node.js 环境);
2.微任务:Promise、MutaionObserver、process.nextTick(Node.js 环境)
注:new Promise中的代码会立即执行,then函数分发到微任务队列,process.nextTick分发到微任务队列Event Queue
任务进入执行栈----同步任务还是异步任务----同步的进入主线程,异步的进入Event Table并注册函数。当指定的事情完成时,Event Table会将这个函数移入Event Queue。主线程内的任务执行完毕为空,会去Event Queue读取对应的函数,进入主线程执行。上述过程会不断重复,也就是常说的Event Loop(事件循环)。
这里有一个简单地宏任务微任务用例
setTimeout(function() {
console.log('宏任务setTimeout'); //先遇到setTimeout,将其回调函数注册后分发到宏任务Event Queue
//如果setTimeout设置时间,那它会先把函数放到宏任务Event Table,等时间到了再放入宏任务Event Queue里面
})
new Promise(function(resolve) {
console.log('微任务promise'); //new Promise函数立即执行
resolve(); //必须resolve执行才能执行then
}).then(function() {
console.log('微任务then'); //then函数分发到微任务Event Queue
})
console.log('主线程console');
//执行顺序结果: 微任务promise、主线程console、微任务then、宏任务setTimeout
Promise 的回调函数不是正常的异步任务,而是微任务(microtask)。它们的区别在于,正常任务追加到下一轮事件循环,微任务追加到本轮事件循环。这意味着,微任务的执行时间一定早于正常任务。并且如果有多个Pomise的回调函数进入微任务队列,那么这些微任务会一起执行完才会去执行排队的宏任务。
事件循环,宏任务,微任务的关系如图所示
另外,resolve()和reject()这两个方法,是可以给promise.then()传递参数的。
案例如下:
在浏览器控制台输出结果为
基于Promise处理多次ajex请求(链式调用)
有了 promise之后,我们可以把多层嵌套调用按照线性的方式进行书写,非常优雅。
也就是说:Promise 可以把原本的多层嵌套调用改进为链式调用。
代码如下:
<script type="text/javascript">
function queryData(url) {
var promise = new Promise((resolve, reject) => {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
if (xhr.readyState != 4) return;
if (xhr.readyState == 4 && xhr.status == 200) {
// 处理正常情况
resolve(xhr.responseText); // xhr.responseText 是从接口拿到的数据
} else {
// 处理异常情况
reject("接口请求失败");
}
};
xhr.responseType = "text"; // 设置返回的数据类型
xhr.open("get", url);
xhr.send(null); // 请求接口
});
return promise;
}
// 发送多个ajax请求并且保证顺序
queryData("https://jsonplaceholder.typicode.com/posts")
.then(
(data1) => {
console.log(JSON.stringify(data1));
// 请求完接口1后,继续请求接口2
return queryData("https://jsonplaceholder.typicode.com/posts");
},
(error1) => {
console.log(error1);
}
)
.then(
(data2) => {
console.log(JSON.stringify(data2));
// 请求完接口2后,继续请求接口3
return queryData("https://jsonplaceholder.typicode.com/posts");
},
(error2) => {
console.log(error2);
}
)
.then(
(data3) => {
// 获取接口3返回的数据
console.log(JSON.stringify(data3));
},
(error3) => {
console.log(error3);
}
);
</script>
这里的url我用的是jsonplaceholder上面的数据,在运行的时候xhr.responseType = "text";这一行代码原本是xhr.responseType = "json";因为我的浏览器不能正常读取json文件,所以讲json改成了text这样就能正常读取了,如果改了之后的代码无法正常运行或者读取不出文件,可以尝试把代码改回json。
可以清楚的看到这里数据是被请求了3次,做了三次输出
Return函数的返回值
Return后面的返回值有两种情况
第一种:返回一个新的Promise实例对象。返回的实例对象会调用下一个.then()
这里我引用的例子和上面的处理ajex请求基本一样
<script type="text/javascript">
/*
基于Promise发送Ajax请求
*/
function queryData(url) {
return new Promise((resolve, reject) => {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState != 4) return;
if (xhr.readyState == 4 && xhr.status == 200) {
// 处理正常情况
resolve(xhr.responseText);
} else {
// 处理异常情况
reject('接口请求失败');
}
};
xhr.responseType = 'text'; // 设置返回的数据类型
xhr.open('get', url);
xhr.send(null); // 请求接口
});
}
// 发送多个ajax请求并且保证顺序
queryData(' https://jsonplaceholder.typicode.com/posts')
.then(
data1 => {
console.log(JSON.stringify(data1));
return queryData(' https://jsonplaceholder.typicode.com/posts ');
},
error1 => {
console.log(error1);
}
)
.then(
data2 => {
console.log(JSON.stringify(data2));
// 这里的 return,返回的是 Promise 实例对象
return new Promise((resolve, reject) => {
resolve('qianguyihao');
});
},
error2 => {
console.log(error2);
}
)
.then(data3 => {
console.log(data3);
});
</script>
第二种:返回普通值,返回的普通值会直接传递给下一个then,通过then参数中的函数的参数接收该值
<script type="text/javascript">
/*
基于Promise发送Ajax请求
*/
function queryData(url) {
return new Promise((resolve, reject) => {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState != 4) return;
if (xhr.readyState == 4 && xhr.status == 200) {
// 处理正常情况
resolve(xhr.responseText);
} else {
// 处理异常情况
reject('接口请求失败');
}
};
xhr.responseType = 'text'; // 设置返回的数据类型
xhr.open('get', url);
xhr.send(null); // 请求接口
});
}
// 发送多个ajax请求并且保证顺序
queryData(' https://jsonplaceholder.typicode.com/posts ')
.then(
data1 => {
console.log(JSON.stringify(data1));
return queryData(' https://jsonplaceholder.typicode.com/posts ');
},
error1 => {
console.log(error1);
}
)
.then(
data2 => {
console.log(JSON.stringify(data2));
// 返回普通值
return 'qianguyihao';
},
error2 => {
console.log(error2);
}
)
/*
既然上方返回的是 普通值,那么,这里的 then 是谁来调用呢?
答案是:这里会产生一个新的 默认的 promise实例,来调用这里的then,确保可以继续进行链式操作。
*/
.then(data3 => {
// 这里的 data3 接收的是 普通值 'qianguyihao'
console.log(data3);
});
</script>
Promise常用的API:实例方法
1.Promise.prototype.then()
Promise 实例具有then方法,也就是说,then方法是定义在原型对象Promise.prototype上的。它的作用是为 Promise 实例添加状态改变时的回调函数。前面说过,then方法的第一个参数是resolved状态的回调函数,第二个参数是rejected状态的回调函数,它们都是可选的。
then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)。因此可以采用链式写法,即then方法后面再调用另一个then方法。
getJSON("/post/1.json").then(function(post) {
return getJSON(post.commentURL);
}).then(function (comments) {
console.log("resolved: ", comments);
}, function (err){
console.log("rejected: ", err);
});
上面代码中,第一个then方法指定的回调函数,返回的是另一个Promise对象。这时,第二个then方法指定的回调函数,就会等待这个新的Promise对象状态发生变化。如果变为resolved,就调用第一个回调函数,如果状态变为rejected,就调用第二个回调函数。
2. Promise.prototype.catch()
Promise.prototype.catch()方法是.then(null, rejection)或.then(undefined, rejection)的别名,用于指定发生错误时的回调函数。
getJSON('/posts.json').then(function(posts) {
// ...
}).catch(function(error) {
// 处理 getJSON 和 前一个回调函数运行时发生的错误
console.log('发生错误!', error);
});
上面代码中,getJSON()方法返回一个 Promise 对象,如果该对象状态变为resolved,则会调用then()方法指定的回调函数;如果异步操作抛出错误,状态就会变为rejected,就会调用catch()方法指定的回调函数,处理这个错误。另外,then()方法指定的回调函数,如果运行中抛出错误,也会被catch()方法捕获。
Promise 对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止。也就是说,错误总是会被下一个catch语句捕获。
getJSON('/post/1.json').then(function(post) {
return getJSON(post.commentURL);
}).then(function(comments) {
// some code
}).catch(function(error) {
// 处理前面三个Promise产生的错误
});
上面代码中,一共有三个 Promise 对象:一个由getJSON()产生,两个由then()产生。它们之中任何一个抛出的错误,都会被最后一个catch()捕获。
一般来说,不要在then()方法里面定义 Reject 状态的回调函数(即then的第二个参数),总是使用catch方法。
不管是then()方法还是catch()方法,都返回了一个新的Promise对象
3. Promise.prototype.finally()
finally()方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。
不管promise最后的状态,在执行完then或catch指定的回调函数以后,都会执行finally方法指定的回调函数。
finally方法的回调函数不接受任何参数,这意味着没有办法知道,前面的 Promise 状态到底是fulfilled还是rejected。这表明,finally方法里面的操作,应该是与状态无关的,不依赖于 Promise 的执行结果。
下面是一个例子,包含了三种实例方法
<script>
function queryData() {
return new Promise((resolve, reject) => {
setTimeout(function() {
var data = { retCode: 0, msg: 'qianguyihao' }; // 接口返回的数据
if (data.retCode == 0) {
// 接口请求成功时调用
resolve(data);
} else {
// 接口请求失败时调用
reject({ retCode: -1, msg: 'network error' });
}
}, 100);
});
}
queryData()
.then(data => {
// 从 resolve 获取正常结果
console.log('接口请求成功时,走这里');
console.log(data);
})
.catch(data => {
// 从 reject 获取异常结果
console.log('接口请求失败时,走这里');
console.log(data);
})
.finally(() => {
console.log('无论接口请求成功与否,都会走这里');
});
</script>
Promise 的常用API:对象方法
1.Promise.all():并发处理多个异步任务,所有任务都执行成功,才能得到结果。
const p = Promise.all([p1, p2, p3]);
p的状态由p1、p2、p3决定,分成两种情况。
(1)只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。
(2)只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。
2.Promise.race(iterable): 并发处理多个异步任务,只要有一个任务执行成功,就能得到结果。
const p = Promise.race([p1, p2, p3]);
只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。
3. Promise.allSettled()
用来确定一组异步操作是否都结束了(不管成功或失败)。所以,它的名字叫做”Settled“,包含了”fulfilled“和”rejected“两种情况。
Promise.allSettled()方法接受一个数组作为参数,数组的每个成员都是一个 Promise 对象,并返回一个新的 Promise 对象。只有等到参数数组的所有 Promise 对象都发生状态变更(不管是fulfilled还是rejected),返回的 Promise 对象才会发生状态变更。
4. Promise.any()
只要参数实例有一个变成fulfilled状态,包装实例就会变成fulfilled状态;如果所有参数实例都变成rejected状态,包装实例就会变成rejected状态。
Promise.any()跟Promise.race()方法很像,只有一点不同,就是Promise.any()不会因为某个 Promise 变成rejected状态而结束,必须等到所有参数 Promise 变成rejected状态才会结束。
async/await的作用与用法
什么是async、await?
async基本用法
async函数返回一个 Promise 对象,可以使用then方法添加回调函数。当函数执行的时候,一旦遇到await就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。
下面是一个例子。
async function getStockPriceByName(name) {
var symbol = await getStockSymbol(name);
var stockPrice = await getStockPrice(symbol);
return stockPrice;
}
getStockPriceByName('goog').then(function (result) {
console.log(result);
});
上面代码是一个获取股票报价的函数,函数前面的async关键字,表明该函数内部有异步操作。调用该函数时,会立即返回一个Promise对象。
async 函数有多种使用形式。
// 函数声明
async function foo() {}
// 函数表达式
const foo = async function () {};
// 对象的方法
let obj = { async foo() {} };
obj.foo().then(...)
// Class 的方法
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('jake').then(…);
// 箭头函数
const foo = async () => {};
async函数返回一个 Promise 对象。
async函数内部return语句返回的值,会成为then方法回调函数的参数。
async函数内部抛出错误,会导致返回的 Promise 对象变为reject状态。抛出的错误对象会被catch方法回调函数接收到。
Promise 对象的状态变化
async函数返回的 Promise 对象,必须等到内部所有await命令后面的 Promise 对象执行完,才会发生状态改变,除非遇到return语句或者抛出错误。也就是说,只有async函数内部的异步操作执行完,才会执行then方法指定的回调函数。
await
正常情况下,await命令后面是一个 Promise 对象。如果不是,会被转成一个立即resolve的 Promise 对象。
await命令后面的 Promise 对象如果变为reject状态,则reject的参数会被catch方法的回调函数接收到。
只要一个await语句后面的 Promise 变为reject,那么整个async函数都会中断执行。
有时,我们希望即使前一个异步操作失败,也不要中断后面的异步操作。这时可以将第一个await放在try...catch结构里面,这样不管这个异步操作是否成功,第二个await都会执行。
另一种方法是await后面的 Promise 对象再跟一个catch方法,处理前面可能出现的错误。
错误处理
如果await后面的异步操作出错,那么等同于async函数返回的 Promise 对象被reject。
防止出错的方法,也是将其放在try...catch代码块之中
如果有多个await命令,可以统一放在try...catch结构中。
使用注意点
第一点, 前面已经说过,await命令后面的Promise对象,运行结果可能是rejected,所以最好把await命令放在try...catch代码块中。
第二点, 多个await命令后面的异步操作,如果不存在继发关系,最好让它们同时触发。
第三点, await命令只能用在async函数之中,如果用在普通函数,就会报错。
async 函数的实现原理
async 函数的实现原理,就是将 Generator 函数和自动执行器,包装在一个函数里。
与其他异步处理方法的比较
我们通过一个例子,来看 async 函数与 Promise的比较。
假定某个 DOM 元素上面,部署了一系列的动画,前一个动画结束,才能开始后一个。如果当中有一个动画出错,就不再往下执行,返回上一个成功执行的动画的返回值。
首先是 Promise 的写法。
function chainAnimationsPromise(elem, animations) {
// 变量ret用来保存上一个动画的返回值
var ret = null;
// 新建一个空的Promise
var p = Promise.resolve();
// 使用then方法,添加所有动画
for(var anim of animations) {
p = p.then(function(val) {
ret = val;
return anim(elem);
});
}
// 返回一个部署了错误捕捉机制的Promise
return p.catch(function(e) {
/* 忽略错误,继续执行 */
}).then(function() {
return ret;
});
}
虽然 Promise 的写法比回调函数的写法大大改进,但是一眼看上去,代码完全都是 Promise 的 API(then、catch等等),操作本身的语义反而不容易看出来。
async 函数的写法。
标签:基本,function,console,log,用法,xhr,Promise,函数 From: https://www.cnblogs.com/dengke1126/p/17298727.htmlasync function chainAnimationsAsync(elem, animations) {
var ret = null;
try {
for(var anim of animations) {
ret = await anim(elem);
}
} catch(e) {
/* 忽略错误,继续执行 */
}
return ret;
}
可以看到Async函数的实现最简洁,最符合语义,几乎没有语义不相关的代码。
参考:
https://www.cnblogs.com/sybboy/p/6420812.html
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/await
https://blog.csdn.net/weixin_44801790/article/details/126345725
https://www.cnblogs.com/qianguyihao/p/12660393.html