异步
- 异步 是与同步 相对的概念。
- 同步:指在 主线程 上排队执行的任务,只有前一个任务执行完毕,才能继续执行下一个任务。也就是调用一旦开始,必须这个调用 返回结果才能继续往后执行。程序的执行顺序和任务排列顺序是一致的。
- 异步
- 是指不进入主线程,而进入 任务队列的任务,只有任务队列通知主线程,某个异步任务可以执行了,该任务才会进入主线程。
- 每一个任务有一个或多个 回调函数。前一个任务结束后,不是执行后一个任务,而是执行回调函数,后一个任务则是不等前一个任务结束就执行。
- 程序的执行顺序和任务的排列顺序是不一致的,异步的。
- setTimeout、setInterval函数、Ajax都是异步操作。
- 异步就是从主线程发射一个子线程来完成任务。
- 同步按你的代码顺序执行,异步不按照代码顺序执行,异步的执行效率更高。
出现原因
js是单线程,为了不浪费时间,提高运行效率。
异步工作原理
主要是依据settimeout(定时执行回调函数)和setinterval(定时执行回调函数并以相同间隔重复执行,可以通过clearInterval取消重复)函数。
js有一个专门处理计时器的模块。js主线程先解决同步任务,与此同时,计时器模块独立运行,将到时的任务放入异步队列,当同步任务解决完以后,js会解决到时的异步队列。
实现异步的方法
JS 异步编程进化史:callback -> promise -> generator -> async + await
- 回调函数
- 事件监听:
- 优点是比较容易理解,可以绑定多个事件,每个事件可以指定多个回调函数,而且可以"去耦合",有利于实现模块化。
- 缺点是整个程序都要变成事件驱动型,运行流程会变得很不清晰。阅读代码的时候,很难看出主流程。
- 发布订阅:性质与"事件监听"类似,但是明显优于后者。因为可以通过查看"消息中心",了解存在多少信号、每个信号有多少订阅者,从而监控程序的运行。
- Promise/A+
- 生成器Generators/ yield
- 语法上可以理解为Generator 函数是一个状态机,封装了多个内部状态。
- Generator还是一个遍历器对象生成函数。可以使用yield暂停函数, 可暂停,next方法可启动,每次返回的是yield后的表达式结果。
- yield表达式本身没有返回值,或者说总是返回undefined。next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值。
- async/await
回调函数
回调函数就是一个函数,它是在我们启动一个异步任务的时候就告诉它:等你完成了这个任务之后要干什么。这样一来主线程几乎不用关心异步任务的状态了,他自己会善始善终。
ajax(url, () => {
// 处理逻辑
ajax(url1, () => {
// 处理逻辑
ajax(url2, () => {
// 处理逻辑
})
})
})
使用原因
js遇到回调函数即会放入异步队列。
回调正是确保一段代码执行完毕之后再执行另一段代码的方式 ,可以确保函数异步执行。
回调函数优点:不会立即执行、是个闭包(可以访问到到其外层定义的变量)
缺点
回调函数是嵌套调用,外部回调函数执行结果是嵌套的回调执行的条件,不便于阅读,不便于异常处理
必须在启动异步任务之前指定所有任务
promise
使用原因
链式调用,避免了回调地域
可以在异步任务启动后绑定新的一致多个回调
属性
promiseState
- promise状态
- 值:
- pending(初始状态,准备状态,未决定的)
- resolve / fullfilled(成功,解决)
- reject(失败,拒绝)
- 状态改变一次后就不会再更改,改变后立即放入微队列内
- 改变方法:
resolve(value)
: pending -> resolvedreject(reson)
: pending -> rejected- 抛出异常:pending -> rejected
promiseResult
- promise结果
- promiseResult 保存异步任务 成功/失败的结果
- 只有resolve和reject可以改变这个值
基本流程
- 新建一个promise对象,状态为pedding
- 执行异步操作,
- 成功:
- 执行resolve()函数,promise对象状态为resolved
- 执行.then,执行onResolved()函数,返回新的promise对象
- 失败
- 执行reject()函数,promise对象状态为rejected
- 执行.then或者.catch,执行onRejected()函数,返回新的promise对象
- 成功:
- 实际运行时,改变状态和指定回调函数(指定而不是执行)的代码先后顺序任意
- 先改变状态后指定回调:
- promise参数中在直接写了resolve()/reject(),即作为同步代码执行
- 在then调用外嵌套了异步函数,即.then作为异步函数执行
- 先指定回调后改变状态:
- 在promise函数中的异步函数内写了resolve()/reject(),作为异步函数时等待(这种情况比较多、例如io、数据库、ajax操作)
- .then为同步
- 先改变状态后指定回调:
- 当状态改变且回调已经指定好后,才会得到数据
语法
//promise参数为函数,该函数的参数默认有resolve、reject两个函数类型参数
new Promise((resolve,reject)=>{
//...期间的同步代码会正常执行,异步代码会正常放入该去的队列
//resolve('成功');改变后调用onResolved函数
//reject('失败');改变后调用onRejected函数
//更改完状态再将相应代码放入微队列,等待同步代码执行完后再执行
//promise和then是对应出现的,then返回的也是promise对象
//then可以一直套用下去,并且then中任务能确保异步的顺序
}).then(onResolved,onRejected);
.catch(onRejected)
.finally()
//也可以是.finally 即不论怎么样都执行 等同于.then(fn,fn)
- 通常用于常规清理
- finally 处理程序没有得到前一个处理程序的结果(它没有参数)。而这个结果被传递给了下一个合适的处理程序。
- 如果 finally 处理程序返回了一些内容,那么这些内容会被忽略。
- 当 finally 抛出 error 时,执行将转到最近的 error 的处理程序。
promise实例对象方法
then
then(onResolved,onRejected);
-
onResolved:成功时的回调
-
onRejected:可选,失败时的回调
-
返回值:
-
一个新的Promise实例,由then指定的回调函数的执行结果决定
- 执行结果为结果抛出异常:返回状态为rejected,结果为抛出异常的promise
- 执行结果为非promise:返回状态为resolved,结果为由then指定的回调函数的执行结果的promise
- 执行结果为promise:返回该由then指定的回调函数的执行结果
- 默认为返回状态为resolved,结果为undefined的Promise
let p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('OK');
}, 1000);
});
p.then(value => {
return new Promise((resolve, reject) => {
resolve(" success" );
});
}).then(value => {//此时为一个状态为resolved,结果为success的promise调用.then
console.log(value); //success
}).then(value => {//调用者没有return,所以为默认值,为一个状态为resolved,结果为undefined的默认Promise调用
console.log(value);//undefined
})
-
-
作用:
- 为promise实例添加状态改变时的回调函数
- 当前面的Promise状态改变时,.then()根据其最终状态,选择特定的状态响应函数执行
-
特性:
- 因为返回一个新的Promise实例,所以它可以链式调用
- 通过then的链式调用串联多个同步/异步任务
-
如果返回新的promise,那么下一级.then()会在新的promise状态改变之后执行
-
如果返回其他任何值,则会立即执行下一级.then()
-
返回一个带状态的primose,可以作为下一个promise的参数
此时状态改变遵循参数的状态而不是调用的
let p1 = new Promise((resolve, reject)=>{
reject("拒绝");
});
new Promise((resolve, reject)=>{
resolve(p1);//即遵循p1而不是resolve
}).then(
msg=>{console. log(msg);},
error=>{console. log(error+'error');
})
-
then的多次调用也会在转变为对应状态时依次执行
let p = new Promise((resolve,reject)=>{
resolve('成功')
})
//当p转变为resolve状态时,即会一次调用以下两个then
//但是如果是reject,因为then中没有指定失败的回调函数,所以不执行(没有对应执行的)
p.then(value =>{
console.log(value)
})
p.then(value =>{
console.log(value+"1")
})
catch
catch(onRejected)
- onRejected:失败时的回调
- 捕获上文同步错误(reject throw),并且可以再次抛出(throw)由后续catch处理
- 只关心失败情况 等同于.then(null,onRejected),类似语法糖
promise函数对象方法
resolve
Promise.resolve(value)
- value为非promise对象时,返回值为成功promise对象
- value为promise对象时,则返回值为该value参数的结果
- 快速返回一个成功/失败的promise对象
reject
Promise.reject(reason)
- 返回结果与reason无关,都为失败的promise对象
- 返回的失败promise对象的结果为reason
all
该方法用于多个promise实例,包装成一个新的promise实例,即等待所有promise准备就绪
Promise.all([p1,p2,p3]);
- 参数为一个包含n个promise的数组
- 返回值:
-
成功则返回一个成功primise对象,结果为包含了p1,p2,p3的返回值的数组,该数组顺序只和源代码有关,和等待时长无关
-
失败则返回一个失败promise对象,结果为失败的promise参数的结果,且会忽略其他promise结果
Promise.all([p1, p2]).then((result) => {
console.log(result) //['成功了', 'success']
}).catch((error) => {
console.log(error) //
})
-
race
Promise.race([p1,p2,p3]);
- 参数为一个包含n个promise的数组
- 返回值为一个promise,状态和结果为第一个完成的promise参数的状态和结果
promise.allSettled
对于每个promise都获取了其状态和value/reason
let urls = [
'https://api.github.com/users/iliakan',
'https://api.github.com/users/remy',
'https://no-such-url'
];
Promise.allSettled(urls.map(url => fetch(url)))
.then(results => { // (*)
results.forEach((result, num) => {
if (result.status == "fulfilled") {
alert(`${urls[num]}: ${result.value.status}`);
}
if (result.status == "rejected") {
alert(`${urls[num]}: ${result.reason}`);
}
});
});
Promise.any
与 Promise.race 类似,区别在于 Promise.any 只等待第一个 成功的 promise,并将这个 fulfilled 的 promise 返回。如果给出的 promise 都 rejected,那么返回的 promise 会带有 AggregateError ------ 一个特殊的 error 对象,在其 errors 属性中存储着所有 promise error。
异常处理
异常穿透现象:只需要在最后一个then后调用catch就可以捕获所有异常
let p = new Promise((resolve, reject) => {
setTimeout(() => {
reject('Err');
}, 1000);
});
p.then(value => {
console.log(111);
}).then(value => {
console.log(222);
}).then(value => {
console.log(333);
}).catch(reason => {
console.warn(reason);
})
中断promise链
-
即中断已经书写好的promise.then链式调用,只执行前面的不执行后面的
-
方法:在回调函数中返回一个pendding状态的promise对象
let p = new Promise((resolve, reject) => {
setTimeout(() => {
reject('Err');
}, 1000);
});p.then(value => {
console.log(111);
return new Promise(()=>{})
}).then(value => {
console.log(222);
}).then(value => {
console.log(333);
}).catch(reason => {
console.warn(reason);
})
封装核心功能
这种返回值为一个 Promise 对象的函数称作 Promise 函数,它常常用于开发基于异步操作的库。
function print(delay, message) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
console.log(message);
resolve();
}, delay);
});
}
print(1000, "First").then(function () {
return print(4000, "Second");
}).then(function () {
print(3000, "Third");
});
- then、catch 和 finally 顺序可以颠倒,但是最好按 then-catch-finally 的顺序编写程序。
- then、finally都可以多次使用,会按顺序执行,但是catch只会执行第一个,除非 catch 块里有异常。所以最好只安排一个 catch块。
- then 块默认会向下顺序执行,return 是不能中断的,可以通过 throw 来跳转至 catch 实现中断。
- 当需要多次顺序执行异步操作的时候,例如,如果想通过异步方法先后检测用户名和密码,需要先异步检测用户名,然后再异步检测密码的情况下, Promise显然优于回调函数。
嵌套返回
缺点:无法取消 Promise,错误需要通过回调函数捕获。
.then(
value > {
return new Promise((resolve,reject) = {//其后的then是对该返回值promise的处理
reject( "处理失败" );
})
.then(
null,
r =console.log(r);
});
//处理失败
回调函数转promise
- 将函数主体套入
new promise ((resolve,reject)=>{...})
中 - 将promise置为一个新函数的返回值 方便后续使用
- 将原函数主体的成功、失败函数改为
resolve(),reject()
,到此封装完成 - 调用新函数,添加.then并在其中写上
resolve(),reject()
的具体内容,处理结果
util.promisify
通过此方法可以将回调函数风格简便的转换成promise风格
//引入util 模块
const util = require('util');
//引入fs模块
const fs = require('fs');
//返回一个新的函数
let mineReadFile = util. promisify(fs.readFile);
mineReadFile('./resource/content.txt'). then(value=>{ ... });|
async+await
-
async/await 函数的实现,就是将 Generator 函数和自动执行器,包装在一个函数里
-
await只在async函数内工作,让js引擎等待直到promise完成并返回结果
-
await 实际上会暂停函数的执行,直到 promise 状态变为 settled,然后以 promise 的结果继续执行。这个行为不会耗费任何 CPU 资源,因为 JavaScript 引擎可以同时处理其他任务:执行其他脚本,处理事件等。
-
相比于 promise.then,它只是获取 promise 的结果的一个更优雅的语法。并且也更易于读写。
async function main(){
try{
//读取第一个文件的内容
let datal = await mineReadFile(' ./ resource/1. html' );
let data2 = await mineReadFile(' . / resource/2. html');
let data3 = await mineReadFile(' ./resource/3.html' );
console.1og(data1 + data2 + data3);
}catch(e){ .. }
返回值
- async确保函数返回promise,会将非promise的返回值包装
- 非promise数据:返回一个成功的,结果为async函数返回值的promise对象
- promise对象:返回该由async指定的回调函数的执行结果
- 抛出异常:返回状态为rejected,结果为该抛出异常的promise
await
- await右侧表达式一般为promise对象,也可以为其他值
- 如果右侧为promise对象,返回的是promise成功的值
- 如果是其他值,将该值作为await返回值
- 可以和promise.all一起使用
let results = await Promise.all([...]);
特点
- await必须在async中,async中可以没有await
- 如果await右侧promise失败了,就会抛出异常,需要
try{}catch{}
捕获-
即在
try{}catch{}
中写await比较保险async function f() {
await Promise.reject(new Error("Whoops!"));
}
即
async function f() {
throw new Error("Whoops!");
}async function f() {
try {
let response = await fetch('http://no-such-url');
} catch(err) {
alert(err); // TypeError: failed to fetch
}
}
f();
-
async+await和Promise对比
- 相同:
- 都是异步编程的一种解决方案
- 都是非阻塞的
- 区别:
- 概念区别
- Promise的出现解决了传统callback函数导致的"地域回调"问题
- async await 也是异步编程的一种解决方案,他遵循的是Generator 函数的语法糖,他拥有内置执行器,不需要额外的调用直接会自动执行并输出结果,它返回的是一个Promise对象。
- 语法区别
- promise是链式调用
- async await代码使得异步代码看起来像同步代码,await的本质是可以提供等同于"同步效果"的等待异步返回能力的语法糖,只有这一句代码执行完,才会执行下一句。
- 概念区别
- async :
- 优点:
- 处理 then 的调用链,能够更清晰准确的写出代码
- 也能优雅地解决回调地狱问题。
- 缺点:
- 因为 await 将异步代码改造成了同步代码,如果多个异步代码没有依赖性却使用了 await 会导致性能上的降低,代码没有依赖性的话,完全可以使用 Promise.all 的方式。
- async await是基于Promise实现的,可以说是改良版的Promise,它不能用于普通的回调函数。
- 优点:
async+await和Generator对比
- 内置执行器。 Generator 函数的执行必须靠执行器,所以才有了 co 函数库,而 async 函数自带执行器。也就是说,async 函数的执行,与普通函数一模一样,只要一行。
- 更广的适用性。 co 函数库约定,yield 命令后面只能是 Thunk 函数或 Promise 对象,而 async 函数的 await 命令后面,可以跟 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时等同于同步操作)。
- 更好的语义。 async 和 await,比起星号和 yield,语义更清楚了。async 表示函数里有异步操作,await 表示紧跟在后面的表达式需要等待结果。