highlight: dark theme: smartblue
其实是在学习Node的时候延伸到Promise的,如果已经比较了解的小伙伴可以直接跳到Promise部分哈~
Node.js是什么
Node.js是运行在服务器端的JavaScript
特点:1 单线程(一个服务员)2 异步 3 非阻塞
Node.js和js有什么区别?
区别:
-
JavaScript是包含ECMAScript,DOM,BOM 而后两者是浏览器独有的, Node.js没有
-
js是执行在浏览器中的语言,浏览器通过v8引擎来编译js称为机器语言,而Node.js就是v8引擎的一个容器 使得js能够脱离浏览器运行
联系:
Node.js借助js的单线程 实现了服务端对开销的降低(不像java来一个请求 就分配一个线程)
通过它我们可以读写电脑上的文件,连接数据库,充当web服务器(而web server的作用就是处理http请求)
如:写了js代码之后node 文件名.js就能输出在终端,而不需要在浏览器中去运行
同步是什么
各方都实时(或者尽可能实时)地收取(而且必要的话也处理或者回复)信息的即时沟通方式,即为同步。
电话即为一个日常的例子:人们都倾向于在使用电话时及时地作出回应。
特点:
- 通常代码都是从上至下一行一行的执行
- 前面的代码不执行后面的也无法执行,所以如果遇到长时间等待的,会阻塞
(堵车,前面车祸了,后面的车都走不了,感觉也很像TCP三次握手和四次挥手)
这样的机制就会出现阻塞的问题
js如何解决同步可能带来的阻塞问题呢?(JAVA & Python通过多线程来解决)
而我们的js本来就是单线程的 用到Node.js中也是单线程 那是怎么解决同步带来的问题的呢?
通过异步的方式来解决,也就是说可能会阻塞的 执行时间长的 我等他先执行着,我先去干别的事
(好像小时候的数学题问你怎么规划时间,煮牛奶5分钟,看电视20分钟
同步就像是我一定要先煮好牛奶 才能去看电视,或者看完电视再去煮牛奶,一共耗时25分钟
而异步就像是我可以在看电视的同时 让电磁炉在旁边煮着牛奶,而5分钟到的时候,闹钟会响,这样就只耗时20分钟)
异步
异步的概念
异步指两个或两个以上的对象或事件不同时存在或发生(或多个相关事物的发生无需等待其前一事物的完成),(说人话:一段代码的执行不影响其他后面的执行)
异步的特点
- 不会阻塞其他代码执行
- 需要回调函数返回结果
如何实现异步
JavaScript解决异步任务的几种方式:
- 回调函数
- 事件监听
- 发布/订阅
- Promise对象
- Generator函数
- async/await(Generator 函数的语法糖)
1 回调函数
函数f1被作为实参传入另一函数f2,并在该外部函数内被调用,用以来完成某些任务的函数,称为回调函数
2 事件监听
采用事件驱动模式。任务的执行不取决于代码的顺序,而取决于某个事件是否发生。
3 发布/订阅
假定,存在一个"信号中心",某个任务执行完成,就向信号中心"发布"(publish)一个信号,其他任务可以向信号中心"订阅"(subscribe)这个信号,从而知道什么时候自己可以开始执行。这就叫做"发布/订阅模式"(publish-subscribe pattern),又称"观察者模式"(observer pattern)。
4 Promise
但是异步也会带来问题,就是它无法像同步一样立马拿到return的结果,如果立马返回,是返回的undefined的(还是刚才的例子,我在看电视的一开始 是没有办法拿到热牛奶的)
那我在异步的情况下如何拿到之前代码执行的结果呢? 通过回调函数(也相当于煮牛奶定的闹钟,煮好了通知我去拿)
但回调函数会带来地狱回调的问题,并且可调试性差,所以用Promise来代替回调给我们返回结果
所以由上面的推导我们知道了
- promise可以帮我们解决异步中的地狱回调函数问题
- promise是一个用来存储数据对象的容器,由于存取方式特殊所以可以将异步调用结果保存到promise中
what:就是一个容器,里面放的就是异步调用结果
how:如何做存取操作的
why:回调函数的形式如果回调太多不方便调试
ad : 帮我们解决地狱回调问题
disad: Promise 的最大问题是代码冗余,原来的任务被Promise 包装了一下,不管什么操作,一眼看去都是一堆 then,原来的语义变得很不清楚
4.1 创建Promise
Promise是一个构造函数,可以实例化一个对象出来p,而在实例化对象的时候需要接收一个参数,这个参数是一个函数类型的参数,里面包裹一个异步操作
const p = new Promise(()=>{})
进一步,这个函数有两个函数类型的形参,分别叫resolve,reject,当包裹的异步任务成 功时调resolve,异步任务失败的时候调reject
const p = new Promise((resolve,reject)=>{
//异步操作代码
setTimeout(()=>{
if(成功){
//console.log("你成功了");
resolve(); //将p的状态设置为成功,对应执行then第一个回调
}
else{
//console.log("你失败了");
reject();//将p的状态设置为失败,对应执行then第二个回调
}
},1000);
})
存储数据通过resolve,reject这两个函数,也就是说resolve函数现在里面存的就是注释掉的"你成功了",那么它在哪里进行内容的输出呢?
取用数据通过Promise的实例promise的方法 then((result)=>{},(erro)=>{}) ,它同样接受两个参数,两个参数都是函数,第一个参数是成功时的回调,第二个参数是失败时的回调
const p = new Promise((resolve,reject)=>{
//异步操作代码
setTimeout(()=>{
if(成功){
//console.log("你成功了");
resolve();
}
else{
//console.log("你失败了");
reject();
}
},1000);
})
p.then((result)=>{
console.log("你成功了");
},(erro)=>{
console.log("你失败了");
})
(what?那还是有可能回调函数嵌套啊....Promise是如何解决的呢?)
取到的原理:Promise中维护了两个隐藏的属性
[[PromiseState]] [[PromiseResult]]
同样的代码用p接收后返回了新的promise对象,但是又没有通过resolve存储值,所以[[PromiseResult]]为undefined
[[PromiseResult]]用来存储数据
[[PromiseState]]用来记录promise的以下三种状态 (且只会被修改一次)
- 待定(pending) :初始状态,既没有被兑现,也没有被拒绝。
- 已兑现(fulfilled) :意味着操作成功完成。
- 已拒绝(rejected) :意味着操作失败。
状态在通过resolve存储数据之后就会发生从pending -> fulfilled的改变
状态在通过reject 存储数据之后就会发生从pending -> rejected的改变
4.2 Promise如何解决回调地狱的?
promise在调用then catch finally这三个方法的时候会返回新的Promise(其实新旧好像都可以,只要满足promise/A+规范就行)并且把回调函数的返回值放到新的Promise中的[[PromiseResult]],通过then catch等继续调用就可以避免地狱回调问题
也就是说我们后面的方法(then和catch)就可以读取上一步的结果,但如果上一步的执行结果不是想要的结果就会被跳过
Promise的静态方法
(静态方法就是类方法 直接通过类去调的,与上面的then,catch,finally实例方法区分开~)
Promise.resolve()
创建一个立即完成的Promise,不要在解析为自身的回调上调用Promise.resolve。这将导致无限递归
//创建立即成功的promise
Promise.resolve(10).then(res => console.log(res));
//等价于
let promise = new Promise((resolve,reject)=>{
resolve(10);
})
promise.then(res =>{console.log(res)});
//MDN上会造成死循环的情况
let thenable = {
then: (resolve, reject) => {
resolve(thenable)
}
}
Promise.resolve(thenable) //这会造成一个死循环
Promise.reject()
创建一个立即拒绝的Promise,使用和上面差不多
Promise.all()
Promise.all() 方法接收一个 promise 的 iterable 类型(注:Array,Map,Set 都属于 ES6 的 iterable 类型)的输入,并且只返回一个Promise实例
其中有一个报错就会报错(讲求一个同生共死)
var p1 = Promise.resolve(3);
var p2 = 1337;
var p3 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, 'foo');
});
Promise.all([p1, p2, p3]).then(values => {
console.log(values); // [3, 1337, "foo"]
});
手写Promise.myAll
- 静态方法,在Promise上写,而不是原型上写 promise.prototype.all是错的
- all的参数(Promise数组) all的返回值(新Promise对象)
- 用数组来记录结果
- 同生共死,只要有一个reject,就返回reject
Promise.myAll = function(list){ //myAll的参数
const results = []; //用来存放返回的结果
let count = 0;
//myAll的返回结果是一个promise对象
return new Promise((resolve,reject)=>{
list.map((promise,index)=>{
promise.then((r)=>{
results[index] = r;
count += 1;
if(count === list.length){
resolve(result);
}
},(reason)=>{
reject(reson);
})
})
})
}
手写简单版Promise
class myPromise{
#status = 'pending' //默认不能改的私有属性
constructor(fn){
this.q = []; //链式调用的成功队列和失败队列
const resolve = (data)=>{
// this.#status = 'fulfilled'
const f1f2 = this.q.shift()
if(!f1f2 || !f1f2[0]) return
const x = f1f2[0].call(undefined,data)
if(x instanceof myPromise){
x.then((data)=>{
//调用下一组队列里的f1
resolve(data)
}
,(reason)=>{
//调用下一组队列里的f2
reject(reason)
})
}else{
//else就是成功了。调用下一组队列里的f1
resolve(x)
}
}
const reject = (reason)=>{
this.#status = 'rejected'
const f1f2 = this.q.shift()
if(!f1f2 || !f1f2[1]) return
const x = f1f2[1].call(undefined,reason)
if(x instanceof myPromise){
x.then((data)=>{
//调用下一组队列里的f1
resolve(data)
}
,(reason)=>{
//调用下一组队列里的f2
reject(reason)
})
}else{
//else就是成功了。调用下一组队列里的f1
resolve(x)
}
}
fn.call(undefined,resolve,reject)
}
//根据promise A+规范then必须返回一个promise
then(f1,f2){
this.q.push([f1,f2]);
}
}
const promise = new myPromise(function(resolve,reject){
setTimeout(function(){
resolve('hi')
},3000);
})
promise.then((data)=>{console.log(data)},(reason)=>{console.error(reason)});
参考文章: