Promise
Promise
Promise 是一种用于异步编程的 JavaScript 对象,主要用于处理异步操作的结果
Promise 导致的问题
- 回调地狱(代码难以阅读)
- 错误处理(无法统一处理错误)
- 多个异步操作(“同步结果”困难)
- Promise 可以使用.then()方法链式处理异步逻辑
- Promise 可以使用.catch()方法处理异步操作失败
- Promise 提供.all()、.race()方法支持处理多个 Promise 对象逻辑
const promise = new Promise((resolve, reject) => {
resolve('成功');
});
promise.then(
(value) => {
console.log('成功');
},
(reason) => {
console.log('失败');
}
);
Promise 的链式调用
- 返回的是一个普通值(非 promise 的值)这个值会被传到外层 then 的下一个 then 的成功中去
- 没有返回值(抛错了),会执行外层的 then 的下一个 then 的失败
- 返回的是 promise ,会去解析返回的 promise 将解析后的值,传递到成功或者失败中(看这个 promise 是什么状态)
Promise 调用形式
其实他的本质就是在我们调⽤这些⽀持链式调⽤的函数的结尾时,他⼜返回了⼀个包含他⾃⼰的对象或者是⼀个新的⾃⼰,这些⽅式都可以实现链式调⽤。
var p = new Promise(function(resolve,reject){
resolve('我是Promise的值')
})
console.log(p) p.then(function(res){
// 该res的结果是resolve传递的参数
console.log(res)
}).then(function(res){
// 该res的结果是undefined
console.log(res)
return '123'
}).then(function(res){
// 该res的结果是123
console.log(res)
return new Promise(function(resolve){
resolve(456)
})
}).then(function(res){
// 该res的结果是456
console.log(res)
return '我是直接返回的结果'
}).then()
.then('我是字符串')
.then(function(res){
// 该res的结果是“我是直接返回的结果”
console.log(res)
})
根据现象我们可以分析出链式调⽤形式
- 只要有 then()并且触发了 resolve,整个链条就会执⾏到结尾,这个过程中的第⼀个回调函数的参数是 resolve 传⼊的值
- 后续每个函数都可以使⽤ return 返回⼀个结果,如果没有返回结果的话下⼀个 then 中回调函数的参数就是 undefined
- 返回结果如果是普通变量,那么这个值就是下⼀个 then 中回调函数的参数
- 如果返回的是⼀个 Promise 对象,那么这个 Promise 对象 resolve 的结果会变成下⼀次 then 中回调的函数的参数
- 如果 then 中传⼊的不是函数或者未传值,Promise 链条并不会中断 then 的链式调⽤,并且在这之前最后⼀次的返回结果,会直接进⼊离它最近的正确的 then 中的回调函数作为参数
中断链式
有两种方式可以中的 Promise 的链式,并触发 catch
var p = new Promise(function(resolve, reject){
resolve('我是Promise的值')
})
console.log(p) p.then(function(res){
console.log(res)
}).then(function(res){
// 有两种⽅式中断Promise
// throw('我是中断的原因')
return Promise.reject('我是中断的原因')
}).then(function(res){
console.log(res)
}).then(function(res){
console.log(res)
}).catch(function(err){
console.log(err)
})
then 函数虽然每次都返回 Promise 对象,来实现链式调⽤,但是 then 函数每次返回的都是⼀个新的 Promise 对象
var p = new Promise(function (resolve, reject) {
resolve('我是Promise的值');
});
var p1 = p.then(function (res) {});
console.log(p);
console.log(p1);
console.log(p1 === p); // 输出 false
Promise 的方法
Promise.resolve()
Promise.resolve()
会返回一个新的 Promise,可以解析传入的 Promise,具有等待效果
const promise = new Promise((resolve, reject) => {
setTimeout(resolve, 500, '完成');
});
Promise.resolve(promise).then((data) => {
console.log(data);
});
Promise.reject()
Promise.reject()
返回一个已拒绝的 Promise
function resolved(result) {
console.log('Resolved');
}
function rejected(result) {
console.error(result);
}
Promise.reject(new Error('fail')).then(resolved, rejected);
Promise.all()
代码中需要使⽤异步流程控制时,可以通过 Promise.then 来实现让异步流程⼀个接⼀个的执⾏,假设实际案例中,某个模块的⻚⾯需要同时调⽤ 3 个服务端接⼝,并保证三个接⼝的数据全部返回后,才能渲染⻚⾯。这种情况如果 a 接⼝耗时 1s、b 接⼝耗时 0.8s、c 接⼝耗时 1.4s,如果只⽤ Promise.then 来执⾏流程控制,可以保证三个接⼝按顺序调⽤结束再渲染⻚⾯,但是如果通过 then 函数的异步控制,必须等待每个接⼝调⽤完毕才能调⽤下⼀个,这样总耗时就是 1+0.8+1.4 = 3.2s。这种累加显然增加了接⼝调⽤的时间消耗,所以 Promise 提供了⼀个 all ⽅法来解决这个问题
Promise.all([promise对象,promise对象,...]).then(回调函数)
const promise1 = Promise.resolve(3);
const promise2 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, 'foo');
});
Promise.all([promise1, promise2]).then((values) => {
// values 为传入的Promise结果集合
console.log(values); // [3, 'foo']
});
Promise.race()
race ⽅法与 all ⽅法使⽤格式相同:
Promise.race([promise对象,promise对象,...]).then(回调函数)
回调函数的参数是前⾯数组中最快⼀个执⾏完毕的 promise 的返回值。所以使⽤ race ⽅法主要的使⽤场景是什么样的呢?举个例⼦,假设我们的⽹站有⼀个播放视频的⻚⾯,通常流媒体播放为了保证⽤户可以获得较低的延迟,都会提供多个媒体数据源。我们希望⽤户在进⼊⽹⻚时,优先展示的是这些数据源中针对当前⽤户速度最快的那⼀个,这时便可以使⽤ Promise.race()来让多个数据源进⾏竞赛,得到竞赛结果后,将延迟最低的数据源⽤于⽤户播放视频的默认数据源,这个场景便是 race 的⼀个典型使⽤场景。
const promise1 = new Promise((resolve, reject) => {
setTimeout(resolve, 500, 'one');
});
const promise2 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, 'two');
});
Promise.race([promise1, promise2]).then((value) => {
// promise2 最先执行完成
console.log(value); // 'two'
});
Promise.prototype.finally()
finally 无论 Promise 成功还是失败都会执行
Promise.resolve().finally(() => {
console.log('执行了');
});
Generator
Generator
对象由生成器函数返回并且它符合可迭代协议和迭代器协议。
function* generator() {
yield 1;
yield 2;
yield 3;
}
const gen = generator(); // "Generator { }"
console.log(gen.next().value); // 1
console.log(gen.next().value); // 2
console.log(gen.next().value); // 3
使用实例
const fs = require('fs/promises');
const path = require('path');
function* readResult() {
const fileName = yield fs.readFile(
path.resolve(__dirname, 'name.txt'),
'utf8'
);
const age = yield fs.readFile(path.resolve(__dirname, 'age.txt'), 'utf8');
return age;
}
function co(it) {
return new Promise((resolve, reject) => {
function next(data) {
const { value, done } = it.next(data);
if (!done) {
Promise.resolve(value).then((data) => {
next(data);
}, reject);
} else {
resolve(value);
}
}
next();
});
}
co(readResult()).then((data) => {
console.log(data);
});
async/await
async
函数是使用 async
关键字声明的函数,并且其中允许使用 await
关键字。async
和 await
关键字让我们可以用一种更简洁的方式写出基于 Promise
的异步行为,而无需刻意地链式调用 promise。
const fs = require("fs/promises");
const path = require("path");
async readResult() {
const age = await fs.readFile(path.resolve(__dirname, "age.txt"), "utf8");
return age
}
事件环
进程
- 进程是系统进行分配资源的最小单位
- 每个进程都是独立的,但是可以通过通信做到进程间互相交互
浏览器是一个多进程模型,对浏览器而言,每个页签都是一个进程。好处就是安全,网页挂了之后不影响其他页签,隔离不能互相访问。
- js 主线程是单线程的
- 网络线程(接口请求)、事件触发线程(调度异步任务)、EventLoop(定时器、事件)是多线程
EventLoop
- 任务也是具备优先级的
- 宏任务:(浏览器提供)脚本执行,ui 渲染,定时器,请求,事件,MessageChannel,setImmediate(ie 独有)
- 微任务:(语言提供的就是微任务)promise.then,mutationObserver,queueMicrotask
- 默认会拿出一个宏任务执行,宏任务执行的时候会产生微任务,当宏任务执行完成后会清空所有的微任务
详见宏任务和微任务