HTTP协议特点

HTTP2 特点 (使用 TCP协议)

  1. 使用二进制传输, 更高效, 更紧凑
  2. 对报头压缩, 降低开销
  3. 多路复用,一个网络连接实现并行请求
  4. 服务器主动推送,减少请求延迟
  5. 默认加密

HTTP3 特点 (QUIC, 使用 UDP 协议)

  1. 减少了握手的延迟
  2. 多路复用,没有 TCP 的阻塞问题
  3. 连接迁移, 当由 wifi 转移到 4G 时, 连接不会被断开
  4. 没有队收阻塞问题

TLS握手

TLS 握手步骤

  1. ClientHello: 客户端发送所支持的 SSL/TLS 最高协议版本号和所支持的加密算法集合及压缩方法集合等信息给服务器端
  2. ServerHello: 服务器端收到客户端信息后,. 选定双方都能够支持的 SSL/TLS 协议版本和加密方法及压缩方法,返回给客户端
  3. SendCertificate (可选): 服务器端发送服务端证书给客户端
  4. RequestCertification (可选): 如果选择双向验证,服务端向客户端请求客户端证书
  5. ServerHelloDone : 服务端通知客户端初始协商结束
  6. ResponseCertificate (可选): 如果选择双向验证,客户端向服务器端发送客户端证书
  7. ClientKeyExchange: 客户端使用服务器端的公钥, 对客户端公钥和密钥种子进行加密, 再发送给服务器端.
  8. CertificateVerify (可选): 如果选择双向验证,客户端本地私钥生成数字签名, 并发送给服务器端, 让其通过收到的客户端公钥进行身份验证.
  9. CreateSecretKey: 通讯双方基于密钥种子等信息生成通讯密钥
  10. ChangeCipherSpec: 客户端通知服务器端已将通讯方式切换到加密模式
  11. Finished: 客户端做好加密通讯准备
  12. ChangeCipherSpec: 服务器端通知客户端已将通讯方式切换到加密模式
  13. Finished: 服务器做好加密通讯的准备
  14. Encrypted/DecryptedData: 双方使用客户端密钥, 通过对称加密算法对通讯内容进行加密
  15. ClosedConnection: 通讯结束后. 任何一方发出断开 SSL 链接的消息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
sequenceDiagram
participant Client as 客户端
participant Service as 服务器端

Client ->> Service: 1. ClientHello
Service ->> Client: 2. ServerHello
Service -->> Client: 3. SendCertificate
Service -->> Client: 4. RequestCertification
Service ->> Client: 5. ServerHelloDone
Client -->> Service: 6. ResponseCertificate
Client ->> Service: 7. ClientKeyExchange
Client -->> Service: 8. CertificateVerify
Client -> Service: 9. CreateSecretKey
Client ->> Service: 10. ChangeCipherSpec
Client ->> Service: 11. Finished
Service ->> Client: 12. ChangeCipherSpec
Service ->> Client: 13. finished
Client -> Service: 14.Encrypted/DecryptedData
Client -> Service: ClosedConnection

async 与 await原理

原代码

1
2
3
4
5
6
7
8
9
10
let a = 0;

let yideng = async () =>{
a = a + await 10;
console.log(a);
}

yideng();

console.log(++a);

babel 编译后代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
"use strict";

function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
try {
var info = gen[key](arg);
var value = info.value;
} catch (error) {
reject(error);
return;
}
if (info.done) {
resolve(value);
} else {
Promise.resolve(value).then(_next, _throw);
}
}

function _asyncToGenerator(fn) {
return function() {
var self = this,
args = arguments;
return new Promise(function(resolve, reject) {
var gen = fn.apply(self, args);
function _next(value) {
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value);
}
function _throw(err) {
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err);
}
_next(undefined);
});
};
}

var a = 0;

var yideng = /*#__PURE__*/ (function() {
var _ref = _asyncToGenerator(
/*#__PURE__*/ regeneratorRuntime.mark(function _callee() {
return regeneratorRuntime.wrap(function _callee$(_context) {
while (1) {
switch ((_context.prev = _context.next)) {
case 0:
_context.t0 = a;
_context.next = 3;
return 10;

case 3:
_context.t1 = _context.sent;
a = _context.t0 + _context.t1;
console.log(a);

case 6:
case "end":
return _context.stop();
}
}
}, _callee);
})
);

return function yideng() {
return _ref.apply(this, arguments);
};
})();

yideng();
console.log(++a);

手写Promise

Promise 初始化

  1. 建立 MyPromise 类, 并建立构造方法及参数 fn
  2. 建立 MyPromise 静态常量, 三种状态 PENDING, FULFILLED, REJECTED
  3. 建立 MyPromise 实例对象 state, 用来存储 promise 实例的状态
  4. 建立 MyPromise 实例对象 value, 用来存储 promise 实例的值和拒因
  5. 建立 MyPromise 实例方法 then, 接收两个参数
    • onFulFilled: 用来处理正常逻辑
    • onRejected: 用来处理异常逻辑

功能点 01: resolve 同步返回的值,要能在 then 方法中接收到

测试用例:

1
2
3
4
5
6
7
8
9
it("同步resolve(1) ", (done) => {
const promise = new MyPromise((resolve, reject) => resolve(1));
promise
.then((data) => {
expect(data).toBe(1);
done();
return 2;
});
});

解决方案:

  1. 在 constructor 中, 初始化状态赋值为 PENDING, 执行构造参数 fn方法, 并传入 resolve 方法,和 reject 方法.用来接收异步返回值和拒因:value
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    constructor(fn) {
    this.state = MyPromise.PENDING;
    this.value = undefined;
    // 异步缓存
    const resolve = (value) => {
    this.state = MyPromise.FULFILLED;
    this.value = value;
    };
    const reject = (value) => {
    this.state = MyPromise.REJECTED;
    this.value = value;
    };
    fn(resolve, reject);
    }
  2. resolve 方法中 将 promise 状态state 赋值为 FULFILLED, reject 方法中将 promise 状态 state 赋值为 REJECTED. 同时接收参数 value.这样就实现了 Promise 的实例化
    1
    const promise = new MyPromise((resolve, reject) => e => resolve(1));
  3. 要完成 then 方法,首先得明确 then 方法的参数和返回值
    • 参数:
      • onFulfilled(成功回调方法): 参数为当前值
      • onRejected(失败回调方法): 参数为拒因
    • 返回值: 新的promise对象,后面要能接着 then
  4. then 方法 onFulfilled传参 this.value; 并执行 resolve 方法
    1
    2
    3
    then(onFulfilled, onRejected) {
    onFulfilled(this.value);
    }

功能点 02: resolve 异步返回的值,要能在 then 方法中接受到

测试用例:

1
2
3
4
5
6
7
8
9
10
it("异步resolve(1) ", (done) => {
const promise = new MyPromise((resolve, reject) =>
setTimeout(resolve, 1, 1)
);
promise
.then((data) => {
expect(data).toBe(1);
done();
})
});

解决方案:

  1. 如果 Promise 在 PENDING 状态,数据还没有回来,就要创建缓存队列 resolveCallbacks,缓存 then 中接收到的方法.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    then(onFulfilled, onRejected) {
    if (this.state === MyPromise.PENDING) {
    this.resolveCallbacks.push((value) => {
    onFulfilled(value);
    });
    } else if (this.state === MyPromise.FULFILLED) {
    onFulfilled(this.value));
    }
    }
  2. 当创建实例时异步调用 resolve() 时, 要遍历 resolveCallbacks,并执行其方法, 传入 this.value 的值.

    1
    2
    3
    4
    5
    const resolve = (value) => {
    this.state = MyPromise.FULFILLED;
    this.value = value;
    this.resolveCallbacks.forEach((fn) => fn(this.value));
    };
  3. resolveCallbacks为数组,是考虑到可以在同一个实例上多次进行 .then() 操作.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // 测试用例
    it("异步resolve(1),同一实例多次 then", (done) => {
    const promise = new MyPromise((resolve, reject) =>
    setTimeout(resolve, 1, 1)
    );

    promise.then((data) => {
    expect(data).toBe(1);
    });
    promise.then((data) => {
    expect(data).toBe(1);
    done();
    });
    });

    功能点 03: resolve 只有第一次生效, 多次在实例化 promise 时后面的 resolve 不生效

测试用例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
it(" resolve 只有第一次生效, 多次在实例化 promise 时后面的 resolve 不生效", (done) => {
const promise = new MyPromise((resolve, reject) => {
setTimeout(resolve, 0, 1);
setTimeout(resolve, 0, 1);
});

const arr = [];
promise.then((data) => {
arr.push(data);
});

setTimeout(() => {
expect(arr.toString()).toBe("1");
done();
}, 1);
});

解决方案:

  1. resolve调用后状态由 PENDING 变成了 FULFILED,由 promise.state 的状态可以判断是不是调用了 resolve 方法
1
2
3
4
5
6
7
const resolve = (value) => {
if (this.state === MyPromise.PENDING) {
this.state = MyPromise.FULFILLED;
this.value = value;
this.resolveCallbacks.forEach((fn) => fn(this.value));
}
};
  1. 同样的,reject 也只允许调用一次

功能点 04: 支持链式调用

测试用例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
it("链式调用", (done) => {
const promise = new MyPromise((resolve, reject) =>
setTimeout(resolve, 0, 1)
);

const arr = [];
promise
.then((data) => {
arr.push(data);
return 2;
})
.then((data) => {
arr.push(data);
});
setTimeout(() => {
expect(arr.toString()).toBe("1,2");
done();
}, 100);
});

解决方案:

  1. 根据使用 Promise 的规范, then 后返回一个新的 promise,所以可以在 then 中 new 一个新的 Promise,并 resolve onFulFilled 的返回结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
then(onFulfilled, onRejected) {
const promise2 = new Promise((resolve, reject) => {
if (this.state === MyPromise.PENDING) {
this.resolveCallbacks.push((value) => {
resolve(onFulfilled(value));
});

this.rejectCallbacks.push((value) => {
resolve(onRejected(value));
});
} else if (this.state === MyPromise.FULFILLED) {
onFulfilled(this.value);
} else if (this.state === MyPromise.REJECTED) {
this.rejectCallbacks.push((value) => {
resolve(onRejected(value));
});
}
});

return promise2;
}

功能点 05: 支持空的 then

测试用例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
it("支持空 then", (done) => {
const promise = new MyPromise((resolve, reject) =>
setTimeout(resolve, 0, 1)
);

const arr = [];
promise.then().then((data) => {
arr.push(data);
});
setTimeout(() => {
expect(arr.toString()).toBe("1");
done();
}, 100);
});

解决方案:

Promise规范

Promise A+ 规范

术语

术语 描述
Promise promise是一个拥有then方法的对象和函数
thenable 是一个定义了then方法的对象和函数,
value(值) 指任何JavaScript的合法指(包括 undefined、thenable、promise)
expection 是使用throw语句抛出的值
reason(拒因) 表示一个promise的拒绝原因

Promise的状态

一个Promise当前状态必须为以下三种状态中的一种: 等待态(Pending)、执行态(FulFilled)、拒绝态(Rejected)

等待态(Pending)

处于等待态时,Promise需要满足一下条件:

  • 可以迁移至执行态或拒绝态

执行态(Fulfilled)

处于执行态时,Promise需要满足一下条件:

  • 不能迁移至其他任何状态
  • 必须拥有一个不可变的终值

拒绝态(Rejected)

处于拒绝态时,promise需要满足一下条件:

  • 不能迁移至其他任何状态
  • 必须拥有一个不可变拒因

Then 方法

一个 promise 必须提供一个 then 方法以访问其当前值\终值\拒因

promise 的 then 方法接受两个参数

1
promise.then(onFulfilled, onRejected)

参数可选

onFulfilledonRejected都是可选参数.

  • 如果 onFulFilled 不是函数,其必须被忽略
  • 如果 onRejected 不是函数,其必须被忽略

onFulFilled 特征

如果 onFulFilled 是函数:

  • 当 promise 执行结束后其必须被调用,其第一个参数为 promise 的终值
  • 当 promise 执行结束前其不可被调用
  • 其调用次数不可超过一次

onRejected 特征

如果 onRejected 是函数

  • 当 promise 被拒绝执行后其必须被调用,其第一个参数为 promise 的拒因
  • 在 promise 被拒绝执行前其不可调用
  • 其调用次数不可超过一次

调用时机

onFulFilled 和 onRejected 只有在执行环境堆栈仅包含平台代码的时候才能被调用

调用要求

onFulFilled 和 onRejected 必须被作为函数调用(即没有 this 值)

多次调用

then 方法可以被同一个 promise 调用多次

  • 当 promise 成功执行时,所有 onFulfilled 需要按照其注册顺序依次回调
  • 当 promise 被拒绝执行时,所有 onRejected 需要按照其注册顺序依次回调

返回

then 方法必须返回一个 promise 对象

1
promise2 = promise1.then(onFulfilled,onRejected);
  • 如果 onFulFilled 或者 onRejected 返回一个值 x,则运行下面 Promise 解决过程 [[Resolve]](promise2, x)
  • 如果 onFulFilled 或者 onRejected 抛出一个异常 e,则 promise2 必须拒绝执行,并返回拒因 e
  • 如果 onFulFilled 不是函数且 promise1 成功执行,promise2 必须成功执行并返回相同的值
  • 如果 onRejected 不是函数且 promise1 拒绝执行, promise2 必须拒绝执行并返回相同的拒因

Promise 解决过程

Promise 解决过程是一个抽象的操作, 其需要输入一个 promise 和一个值,我们表示[[Resolve]](promise, x),如果 x 有 then 方法且看上去想一个 Promise, 解决程序即尝试使 promise 接受 x 的状态;否则其用 x 的值来执行 promise

这种 thenable 的特性使得 Promise 的实现更具通用性: 只要其暴露出一个遵循 Promise/A+协议的 then 方法即可; 这同时也使遵循 Promise/A+ 规范的实现与那些不太规范但可用的实现能良好共存

运行[[Resolve]](promise, x)遵循以下步骤:

x 与 promise 相等

如果 promise 和 x 指向同一对象,以 TypeError 为拒因拒绝执行 promise

x 为 Promise

如果 x 为 Promise,则使 promise 接受 x 的状态

  • 如果 x 处于等待态, promise 需要保持为等待态直至 x 被执行或拒绝
  • 如果 x 处于执行态, 用同样的值执行 promise
  • 如果 x 处于拒绝态, 用同样的拒因拒绝 promise

x 为对象或函数

如果 x 为对象或者函数:

  • 把 x.then 赋值给 then
  • 如果取 x.then 的值时抛出错误 e, 则以 e 为拒因拒绝 promise
  • 如果 then 是函数, 将 x 作为函数的作用域 this 调用之. 传递两个回调函数作为参数,第一个参数叫做 resolvePromise, 第二个参数叫做 rejectPromise
    1. 如果 resolvePromise 以值 y 为参数被调用,则运行[[Resolve]](promise, y)
    2. 如果 rejectPromise 以拒因 r 作为参数被调用, 则以拒因 r 拒绝 promise
    3. 如果 resolvePromise 和 rejectPromise 均被调用,或者被同一参数调用了多次,则优先采用首次调用并忽略剩下的调用
    4. 如果调用 then 放大抛出异常 e:
      • 如果 resolvePromise 或 rejectPromise 已经被调用,则忽略之
      • 否则以 e 为拒因拒绝 promise
    5. 如果 then 不是函数,以 x 为参数执行 promise
  • 如果 x 不为对象或者函数,以 x 为参数执行 promise

如果-个 promise 被一个循环的 thenable 链中的对象解决,而[[Resolve]](promise, thenable)的递归性质又使得其被再次调用,根据上述算法将会陷入无限递归之中.算法虽不强制要求,但也鼓励实施者检测这样的递归存在, 若检测到存在则以一个可识别的 TypeError 为拒因来拒绝 promise

参考资料

  1. Promise/A+规范 英文原版
  2. Promise/A+规范 中文版本
  3. Promise 实现

CORS-简单请求与复杂请求

CORS

CORS即Cross Origin Resource Sharing (跨来源资源共享), 通俗说就是我们所熟知的跨域请求,
众所周知,在以前,跨域可以采用代理、JSONP等方式,而在Nodern浏览器前,一切终将是过去,应为有了CORS

CORS 通过服务器端设置Access-Control-Allow-Origin 响应头,即可使指定来源像同源接口一样访问,

CORS 分成两类: 1. 简单请求, 2. 复杂请求

简单请求

HTTP方法为

  • HEAD
  • GET
  • POST

请求头不能超出一下字段

  • Accept
  • Accept-Language
  • Content-Language
  • Last-Event-ID
  • Content-Type,但仅支持一下列
    1. application/x-www-form-urlencoded
    2. multipart/form-data
    3. text/plain

任何一个不满足上述要求的请求,即被认为是复杂请求.一个复杂请求不仅有包含通信内容的请求,同时也包含预请求(preflight request)

简单请求的发送从代码上来看和普通的XHR没有太大的区别,但是HTTP头当中要求总是包含一个域(origin)的信息, 该域包含协议名、地址、以及一个可选的端口.不过这一项实际是由浏览器代为发送,并不是开发者代码可以触及到的

简单请求部分解释

  • Access-Control-Allow-Origin(必含)—不可省略,否则请求按失败处理.该项控制数据可见范围,如果希望所有人可见,可以填写“*”
  • Access-Control-Allow-Credentials(可选)—该项标志请求当中是否包含cookies信息,只有一个可选值true,如果不包含cookies,请略去该项.这一项与XmlHttpRequest2对象中的withCredentials属性应保持一致, 即withCredentiaks为true时,该项也为true;
  • Access-Control-Expose-Heads(可选) — 该项确定XmlHttpRequest2对象中getResponseHeader()方法所能获取的额外信息,通常只能获取到如下信息
    1. Cache-Control
    2. Content-Language
    3. Content-Type
    4. Expires
    5. Last-Modified
    6. Pragma
    7. 当你需要访问额外信息是,就需要在这项当中填写并以逗号进行分隔

如果仅仅是简单请求,那么即便不用CORS也没有什么大不了,但是CORS的复杂请求就令CORS显的更加有用了,简单来说,任何不满足上述简单请求要求的请求,都属于复杂请求,比如PUT、DELETE、等HTTP动作, 或发送Content-Type: application/json的内容

复杂请求

复杂请求表面上看起来和简单请求使用差不多,但实际上浏览器发送了不止一个请求.其中最先发送的是一种“预请求”,此时作为服务器端,也需要返回“预回应”作为响应.预请求实际上是对服务器的一种权限请求,只有当预请求成功返回.实际请求才开始执行.

预请求以OPTIONS形式发送,当中同样包含域,并且还包含了两项CORS特有内容

  • Access-Control-Request-Method — 该项内容是实际请求的种类,可以是GET、POST之类的简单请求,也可以是PUT、DELETE等等
  • Access-Control-Request-Headers — 该项是一个以逗号分隔的列表,当中是复杂请求所使用的头部

显而易见,这个预请求实际上就是在为之后的实际请求发送一个权限请求,在预请求应返回的内容当中, 服务端应当对这两项进行回复, 让浏览器确定请求是否能够成功完成

复杂请求的部分响应头及解释

  • Access-Control-Allow-Origin(必含) — 和简单请求一样,必须包含域
  • Access-Control-Allow-Method(必含) — 这是对预请求当中Access-Control-Request-Method的回复,这一回复将是以逗号分隔的列表.尽管客户端或许只请求某一个方法,但服务器端仍然可以返回所有允许的方法,以便客户端将其缓存.
  • Access-Control-Allow-Headers(当预请求中包含Access-Control-Request-Headers时必须包含) — 这是对预请求当中Access-Control-Requests-Headers的回复,和上面一样是以逗号分隔的列表,可以返回所有支持的头部.这里在实际使用中有遇到,所有支持的头部一时可能不能完全写出来,而不想在这一层做过多的判断,没有关系,实际上通过request的header可以直接把取到Access-Control-Request-Headers,直接把对应的value设置到Access-Control-Request-Headers即可
  • Access-Control-Allow-Credentials(可选) — 和简单请求当中的一样
  • Access-Control-Max-Age(可选) — 以秒为单位的缓存时间,预请求发出并非没有消耗, 允许时应当尽可能缓存

一旦预回应如期而至,所请求的权限也就满足,则实际请求开始发送

兼容版本IE11以后,所以移动端网站可以放心使用