最近在写【重拾前端】系列,下面有几个快速通道,大家自取
前言
老规矩,还是先了解一下什么是异步。异步其实是一个相对比较高级的一个概念。
通常来说,程序都是顺序执行,同一时刻只会发生一件事。如果一个函数依赖于另一个函数的结果,它只能等待那个函数结束才能继续执行,从用户的角度来说,整个程序才算运行完毕.
如果说一个事情需要等待上一件事情做完才能做,但是他们之前又没有强耦合关系。这样就在那里干等就毫无意义。特别是现在计算机普遍都有多核CPU的时代。
举一个形象的🌰。
我们现在要去吃海底捞(海底捞打钱!),我们发现要排队,需要排2个小时左右,这个时候我们要干嘛?难道是干等吗?肯定不是!那样蠢蠢的硬等很呆,我们可能会拿出手机刷刷朋友圈,或者玩一把紧张刺激的王者荣耀又或者去看一场电影等等…在我们做这些事情的时候我们的排队并没有被耽误,甚至可以和服务员说快到了打个电话给我(这个是常见的callback方法,待会讨论)
这样你可以同时完成其他工作,这就是异步编程的出发点。
本文总结了几个异步的方案,理解它们可以让你写出结构更合理、性能更出色、维护更方便的Javascript程序。
异步callback
上来就是一个英俊的例子
var response = fetch('myImage.png');
var blob = response.blob();
没有人可以完美预测到每种网络情况下这个response,是否可以完美获得fetch获得的内容,很有可能会报错。
这个时候我们的callback就✨bu ling bu ling的登场啦。
有些小伙伴可能觉得callback似乎和自己没有太大关系。。。emmmm…
btn.addEventListener('click', () => {
alert('You clicked me!');
});
这样呢?有没有感觉有点儿熟悉了,其他这个就相当于react中的
<div οnclick={() =>{alert('You clicked me!');}}>
点我
</div>
是不是很熟悉,这个其实就是最常见的callback(回调函数)。
又或者比较常见的
setTimeout(() => {
alert('You clicked me!');
},2000)
很常见,大致意思就是2秒钟之后会回调一下这个匿名函数,虽然不一定会准确执行,这个涉及到事件循环,感兴趣的可以去找一下我前阵子写的事件循环😊。
发布-订阅模式
发布-订阅模式:又称为观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状态改变时,所有依赖于它的对象都将得到通知。
在写React的时候,有一个非常非常非常常见的🌰:
import React, {useState, useEffect} from 'react';
const test = () => {
const [derrick, setDerrick] = useState(0)
useEffect(()=> {
console.log(derrick)
}, [derrick])
return(
<button onClick={() => {setDerrick(derrick+1)}}>点我</button>
)
}
useEffect订阅了derrick这个变量的变化,然后监听到变化之后就会进行一次log,打印出derrick变量的值。
Promise
回调救星
之前介绍过回调函数这种异步编程。
加入我希望在A执行完之后执行B,然后执行C,然后执行D…最后执行Z
写出来的代码可能是这样的
var sayhello = function (name, callback) {
setTimeout(function () {
console.log(name);
callback();
}, 1000);
}
sayhello("first", function () {
sayhello("second", function () {
sayhello("third", function () {
console.log("end");
});
});
});
像本例一样嵌套多个方法调用会创建错综复杂的代码,会难以理解与调试。当想要实现更复 杂的功能时,回调函数也会存在问题:若想让两个异步操作并行运行,并且在它们都结束后 提醒你,那该怎么做?若想同时启动两个异步操作,但只采用首个结束的结果,那又该怎么 做?
在这些情况下,你需要追踪多个回调函数并做清理操作, 不过好在promise横空出世拯救了世界。
涉及面试题
promise用过吗?请你介绍一下promise。
其实这里很多同学可能就不知道怎么介绍了,可能只停留在怎么用。我们可以从几个方面来介绍。
- promise是什么?
- promise解决了什么问题
- promise的优缺点
promise是什么?
所谓Promise
,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。
promise解决了什么问题?
- 解决可读性的问题
- 解决信任问题
可读性
可读性,之前的回调地狱promise就可以优雅的解决。(还有更加优雅的,待会会介绍。)
var sayhello = function (name) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
console.log(name);
resolve(); //在异步操作执行完后执行 resolve() 函数
}, 10000);
});
}
sayhello("first").then(function () {
return sayhello("second"); //仍然返回一个 Promise 对象
}).then(function () {
return sayhello("third");
}).then(function () {
console.log('end');
}).catch(function (err) {
console.log(err);
})
信任问题
这个问题可以用promise的几个特点来解释:
(1)对象的状态不受外界影响。
Promise
对象代表一个异步操作,有三种状态:pending
(进行中)、fulfilled
(已成功)和rejected
(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise
这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。
Promise
对象的状态改变,只有两种可能:从pending
变为fulfilled
和从pending
变为rejected
。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对Promise
对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。
由于Promise只能被决议一次,且决议之后无法改变,所以,即便是多次回调,也不会影响结果,决议之后的调用都会被忽略。
//信任问题演示
//回调
function method(cb){
setTimeout(function(){
cb && cb();
//因为某些bug导致某个函数多执行了一次
cb && cb();
},1000);
}
//promise
function method2(){
return new Promise(resolve=>{
setTimeout(function(){
resolve();
//resolve成功调用一次之后,后面的不会再执行
resolve();
},1000);
})
}
控制反转。体现在对于函数的把控,比如this等。
//回调
function method(cb){
setTimeout(function(){
cb && cb.call({
a:1,b:2});//执行回调,但是添油加醋
},1000);
}
//promise
function method2(){
return new Promise(resolve=>{
setTimeout(function(){
resolve();//调用的resolve都是自己写的,改善了控制反转的问题
},1000);
})
}
手撕A+规范的promise
面试题:你知道promise的A+规范吗?(本人在浩鲸科技被问到的,当时答的支支吾吾。。。)
我何止是知道,我甚至可以手写一个符合A+规范的promise!
A+规范快速入口,点我
function MyPromise(executor) {
// 报错this的指向,后面可能会有回调函数,然后会隐式丢失啊之类的。
let self = this;
// 当前的状态(fulfilled/rejected/pending),默认值为pending
self.status = 'pending';
// 存储成功的值
self.value = null;
// 存储失败的值
self.reason = null;
function resolve(value) {
// 2.1.1规定只有在pending状态的时候才可以转变
if(self.status === 'pending') {
// 存下成功的值
self.value = value
self.status = 'fulfilled';
}
}
function reject(reason) {
// 2.1.1规定只有在pending状态的时候才可以转变
if(self.status === 'pending') {
// 存下失败的值
self.reason = reason
self.status