小哥哥、小姐姐,大家好,请把手伸出来,我给大家点东西。javascript
咱们都知道js是单线程语言,这就致使了会有同步异步的概念。所谓同步,就是指脚本解释器在解析代码时,从上往下一行一行解释,第一行解释不完,就不去解释第二行。所谓异步,就是指,当解释到某一行中,发现有异步方法(好比settimeout、ajax、DOM点击事件等等),解释器不会去等待异步方法执行完,再往下解释。而是,将异步任务放到任务队列,当全部的同步代码所有执行完,也就是主线程没有可执行的代码了,就回去任务队列拿出以前遇到的异步任务,执行,执行完毕后再去任务队列调取下一个任务,如此循环。java
一图胜千言node
相信你们对回调函数已经不陌生了,函数A做为参数被传递到函数B里,那么函数A就是回调函数。有什么用呢?请看代码ajax
let doSomething = () => { console.log('do something') }
setTimeout(doSomething, 500);
console.log('a');
复制代码
声明了一个
doSomething
函数,并做为第一个参数传递给了setTimeout
函数,setTimeout
函数会在合适的时机执行它。达到了异步编程的目的。这种方式用处有不少,node.js
有大部分api
都是经过回调来实现异步编程的。编程
promise
写法回调函数这种形式有一个缺点,那就是若是异步任务比较多的话,而且多任务执行有前后顺序,那么回调函数很容易就造成多层嵌套。以下:api
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。promise
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
函数有另一个功能,那就是能够暂停,不像普通函数,只要一执行,那就会一口气执行完。bash
async
+ await
写法async function doSomething() {
await doA();
await doB();
await doC();
await doD();
}
doSomething();
复制代码
是否是发现,这种写法更加简洁,就像在写同步代码同样。异步
先来看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) {
}
复制代码
接下来咱们一个一个方法去攻破。
resolve
、 reject
方法resolve
方法须要完成的事情是:
resolved
self.value
的值self.onResolvedCallbacks
,并一一执行。reject
方法须要完成的事情是
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))
}
复制代码
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
才生效。
先看一下Promise/A+规范中对then
方法的返回值描述。
then
方法必须返回一个
promise
,以实现链式调用。如今咱们须要关注的是,调用
then
方法时,传入的第一个参数(
onFulfilled
)的返回值问题。若是是一个普通值,那咱们就把它继续传递下去,传递给then方法返回的
promise
里;若是是一个新的
promise
的话,那就须要将新的
promise
和
then
方法返回的
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已经支持链式调用了。我但愿阅读本文的你,先去读懂那张图,必定要看懂,知道本身在干什么,就是思路要清晰,而后再去写代码。我刚接触的时候,就是一步步去捋思路,而后辅助画图去理解。用了好几天才弄懂。
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);
})
}
复制代码
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);
});
复制代码
说了不少,但愿大家理解了,若是文中有错误或者你们有不懂的地方,欢迎留言。