小哥哥、小姐姐,你们好,请把手伸出来,我给你们点东西。
1、JavaScript异步编程
- 同步与异步
- 回调函数
- promise
- generator
- async+await
2、写一个符合规范的promise库
1、JavaScript异步编程
1-1、同步与异步
我们都知道js是单线程语言,这就导致了会有同步异步的概念。所谓同步,就是指脚本解释器在解析代码时,从上往下一行一行解释,第一行解释不完,就不去解释第二行。所谓异步,就是指,当解释到某一行中,发现有异步方法(比如settimeout、ajax、DOM点击事件等等),解释器不会去等待异步方法执行完,再往下解释。而是,将异步任务放到任务队列,当所有的同步代码全部执行完,也就是主线程没有可执行的代码了,就回去任务队列拿出之前遇到的异步任务,执行,执行完毕后再去任务队列调取下一个任务,如此循环。
一图胜千言
一次只能服务一个人,请排好队。谁事儿多也没办法,后面只能等着。 三个人同时吃饭,这是异步。如果是同步的话,只能一个人吃完下一个人再吃。1-2、回调函数
相信大家对回调函数已经不陌生了,函数A作为参数被传递到函数B里,那么函数A就是回调函数。有什么用呢?请看代码
let doSomething = () => { console.log('do something') }
setTimeout(doSomething, 500);
console.log('a');
声明了一个
doSomething
函数,并作为第一个参数传递给了setTimeout
函数,setTimeout
函数会在合适的时机执行它。达到了异步编程的目的。这种方式用处有很多,node.js
有大部分api
都是通过回调来实现异步编程的。
1-3、promise
写法
回调函数这种形式有一个缺点,那就是如果异步任务比较多的话,并且多任务执行有先后顺序,那么回调函数很容易就形成多层嵌套。如下:
function doA() { }
function doB() { }
function doC() { }
function doD() { }
doA(function () {
doB(function () {
doC(function () {
doD(function () {
})
})
})
})
当改用promise后,瞬间清爽了许多。
new Promise(function(resolve,reject){
resolve();//在合适的时机出发resolve
})
.then(doA,null)
.then(doB,null)
.then(doC,null)
.then(doD,null)
这也是这篇文章的重点讲解内容,一会我会一步一步按照规范编写一个promise库。彻底搞懂promise。
1-4、generator
写法
function* gen() {
let a = yield doA();
let b = yield doB();
let c = yield doC();
let d = yield doD();
}
let it = gen();//it是一个迭代器
it.next();//{ value: undefined, done: false }
it.next();{ value: undefined, done: false }
it.next();{ value: undefined, done: false }
it.next();{ value: undefined, done: false }
it.next();{ value: undefined, done: true }
可以看到,generator
函数有另外一个功能,那就是可以暂停,不像普通函数,只要一执行,那就会一口气执行完。
1-5、async
+ await
写法
async function doSomething() {
await doA();
await doB();
await doC();
await doD();
}
doSomething();
是不是发现,这种写法更加简洁,就像在写同步代码一样。
2、写一个符合规范的promise库
2-1、实现最简单的一个Promise类
先来看Promise
的用法,然后根据用法一步步编写Promise
类
let p1 = new Promise(function (resolve, reject) {
})
p1.then(function (data) {
console.log(data)
}, function (err) {
console.log(err)
})
在实例化一个Promise
时,传入一个函数作为参数,该函数接受两个参数,分别为resolve
,reject
,然后按照Promise/A+规范一个Promise类应该包含如下状态
status
value
reason
onResolvedCallbacks
onRejectedCallbacks
我们很容易就写出了Promise
的原型。代码如下:
function Promise(executor) {
let self = this;
self.status = 'pending';
self.value = undefined;
self.reason = undefined;
self.onResolvedCallbacks = [];
self.onRejectedCallbacks = [];
function resolve() { }
function reject() { }
executor(resolve, reject);
}
Promise.prototype.then = function (onFulfilled, onRejected) {
}
接下来我们一个一个方法去攻破。
2-2、 实现resolve
、 reject
方法
resolve
方法需要完成的事情是:
- 将Promise的状态置为
resolved
- 更改
self.value
的值 - 通知
self.onResolvedCallbacks
,并一一执行。
reject
方法需要完成的事情是
- 将Promise的状态置为
rejectd
- 更改
self.reason
的值 - 通知
self.onRejectedCallbacks
,并一一执行。 代码如下:
function resolve(value) {
self.status = 'resolved';
self.value = value;
self.onResolvedCallbacks.forEach(item => item(value))
}
function reject(reason) {
self.status = 'rejected';
self.reason = reason;
self.onRejectedCallbacks.forEach(item => item(reason))
}
2-3、 实现then方法
then
方法的作用是收集到成功、失败的回调函数,将他们分别添加到成功和失败的数组中。也就是代码中,我们需要将onFulfilled
添加到self.onResolvedCallbacks
里,将onRejected
添加到self.onRejectedCallbacks
里。
Promise.prototype.then = function (onFulfilled, onRejected) {
this.onResolvedCallbacks.push(onFulfilled);
this.onRejectedCallbacks.push(onRejected);
}
写到这里,这个Promise
其实已经可以用了,不过还有一个潜在的问题。那就是,当resolve
方法被同步调用时,通过then
方法加入到队列的函数没有被执行。只有resolve
被异步调用时才会被执行,为什么呢。因为这里的then
是同步的,resolve
也被同步调用的话,那肯定是,先执行resolve
后执行then
,换句话说就是,先执行回调,后添加回调,这不是我们想看到的,要达到先添加回调,后执行回调的效果,我们稍作修改。
function resolve(value) {
setTimeout(() => {
if (self.status === 'pending') {
self.status = 'resolved';
self.value = value;
self.onResolvedCallbacks.forEach(item => item(value))
}
});
}
function reject(reason) {
setTimeout(function () {
if (self.status == 'pending') {
self.value = value;
self.status = 'rejected';
self.onRejectedCallbacks.forEach(item => item(value));
}
});
}
这里加入了状态判断,因为当Promise
的状态一旦确定,就不能更改,所以状态只能是pending
时,resolve
和reject
才生效。
2-4、实现链式调用
先看一下Promise/A+规范中对then
方法的返回值描述。
then
方法必须返回一个
promise
,以实现链式调用。现在我们需要关注的是,调用
then
方法时,传入的第一个参数(
onFulfilled
)的返回值问题。如果是一个普通值,那我们就把它继续传递下去,传递给then方法返回的
promise
里;如果是一个新的
promise
的话,那就需要将新的
promise
和
then
方法返回的
promise
关联起来。 具体如何关联,咱们慢慢来,这里有点绕,我先上张图看图说话。
图中有部分代码只需要注意三个
promise,
p1
、
x
、
p2
,为了不造成混淆,这三个东西我一次标到了图的左部分,他们是对应的。请大家先明白一句话,然后我们开始说。
调用promise的resolve方法,会执行该promise的then函数的第一个参数
(这里我那resolve举例,reject道理一样,就不赘述了。)
看图,请看图。
调用p1的resolve方法,那么a就会被执行。
调用p2的resolve方法,那么c就会被执行。
也就是说,你想执行a或者b或者c或者d,那么你得找到它属于哪个promise,例如:图中,a b 属于p1,c d属于p2.这个关系必须要明确。
假设你已经理解了上面的话,现在我们面临的问题来了。
c d 本来是属于p2的,执行还是不执行也得看p2调不调用resolve、reject。 现在要让c d执不执行不看p2了,得看x。为什么要看x,因为x这个回调函数是用户传递的,用户的意思是:我让这个回调返回一个promise,然后继续使用then方法添加成功或失败的回调,而且这两个回调啥时候执行,得看我返回的那个promise。 反应到图中就是这个意思:c d何时执行,看x何时调用resolve、reject。
希望你理解了。...继续
破解方法:将p2的resolve、reject放入到x的then方法里。 解释一下:x的resolve、reject是暴露给用户的,也就是说,这两个方法的执行权在用户手里,当用户执行resolve时,其实就执行了x的then方法的第一个参数,而x的then方法的第一个参数正好是p2的resolve,p2的resolve就被执行了,p2的resolve一执行,那么c就被执行了。就实现了x的resolve、reject控制着c d的执行与否。
说了这么多,上代码吧还是,改造后的then方法如下,加入了状态判断,错误捕获
Promise.prototype.then = function (onFulfilled, onRejected) {
let promise2;
let self = this;
if (self.status === 'resolve') {
promise2 = Promise(function (resolve, reject) {
setTimeout(() => {
try {
let x = onFulfilled(self.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
});
})
}
if (self.satus === 'rejected') {
promise2 = new Promise(function (resolve, reject) {
setTimeout(function () {
try {
let x = onRejected(self.reason);
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e);
}
})
})
}
if (self.status === 'pending') {
promise2 = new Promise(function (resolve, reject) {
self.onResolvedCallbacks.push(function (value) {
try {
let x = onFulfilled(value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
});
self.onRejectedCallbacks.push(function (reason) {
try {
let x = onRejected(reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
})
})
}
return promise2;
}
再把resolvePromise
方法写一下,因为多个地方用到了,所以就单独封装了。
function resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) {
return reject(new TypeError('循环引用'));
}
let then, called;
if (x != null && ((typeof x == 'object' || typeof x == 'function'))) {
try {
then = x.then;
if (typeof then == 'function') {
then.call(x, function (y) {
if (called) return;
called = true;
resolvePromise(promise2, y, resolve, reject);
}, function (r) {
if (called) return;
called = true;
reject(r);
});
} else {
resolve(x);
}
} catch (e) {
if (called) return;
called = true;
reject(e);
}
} else {
resolve(x);
}
}
到这里,我们写的promise已经支持链式调用了。我希望阅读本文的你,先去读懂那张图,一定要看懂,知道自己在干什么,就是思路要清晰,然后再去写代码。我刚接触的时候,就是一步步去捋思路,然后辅助画图去理解。用了好几天才弄懂。
2-5、实现catch
、all
、race
、Promise.resolve()
、Promise.reject()
相比较then方法,这几个方法就轻松许多了。直接上代码了,我会把注释写到代码里边 catch
Promise.prototype.catch = function (onRejected) {
return this.then(null, onRejected);//原来这么简单
}
all
Promise.all = function (promises) {
return new Promise(function (resolve, reject) {
let result = [];//结果集
let count = 0;//计数器,用来记录promise有没有执行完
for (let i = 0; i < promises.length; i++) {
promises[i].then(function (data) {
result[i] = data;
if (++count == promises.length) {
resolve(result);//计数器满足条件时,触发resolve
}
}, function (err) {
reject(err);
});
}
});
}
race
// 只要有一个promise成功了 就算成功。如果第一个失败了就失败了
Promise.race = function (promises) {
return new Promise(function (resolve, reject) {
for (var i = 0; i < promises.length; i++) {
promises[i].then(resolve,reject)
}
})
}
Promise.resolve()、Promise.reject()
// 生成一个成功的promise
Promise.resolve = function (value) {
return new Promise(function (resolve, reject) {
resolve(value);
})
}
// 生成一个失败的promise
Promise.reject = function (reason) {
return new Promise(function (resolve, reject) {
reject(reason);
})
}
2-6、Promise
的语法糖
Promise.deferred = Promise.defer = function () {
var defer = {};
defer.promise = new Promise(function (resolve, reject) {
defer.resolve = resolve;
defer.reject = reject;
})
return defer;
}
看一个例子
let fs = require('fs');
let Promise = require('./promise');
function read() {
// 好处就是解决嵌套问题
// 坏处错误处理不方便了
let defer = Promise.defer();
fs.readFile('./2.promise.js/a.txt','utf8',(err,data)=>{
if(err)defer.reject(err);
defer.resolve(data)
});
return defer.promise;
}
read().then(data=>{
console.log(data);
});
说了很多,希望你们理解了,如果文中有错误或者大家有不懂的地方,欢迎留言。