异步的开端-promise
当 promise 已经成为日常开发必不可少的一部分。
你是否了解为什么要使用promise?promise 的执行机制?以及 promise 的发展历史?
1. 为什么使用 promise ?
先看一段代码:
const fs = require('fs');
fs.readFile('./name.txt', 'utf-8', (err, data) => {
if (err) {
console.log(err);
return;
}
if (data) {
fs.readFile(data, 'utf-8', (err, data) => {
fs.readFile(data, 'utf-8', (err, data) => {
console.log(data);
});
});
}
});
在没有 promise 时,我们使用回调的方式来管理异步,使用大量的回调函数嵌套后,
让我们的程序变的越来越臃肿,难于维护,功能无法拓展。
再看一段代码:
try {
console.log(a);
} catch (error) {
console.log(error.message); // a is not defined
}
使用 try-catch 时,我们可以优雅的捕获错误,然后在其中掺杂异步时,会发生什么?
try {
setTimeout(() => {
console.log(a);
}, 30)
} catch (error) {
console.log(error.message); // 直接报错,无法捕获错误
}
是的,你没看错,try-catch 是无法捕获异步代码的错误的,它只能捕获同步代码的错误。
还有回调是无法处理同步并发问题的,以上就是我们常说的回调地狱问题。
2. 如何解决回调地狱问题?
在 jQuery 中使用 Callbacks 进行回调管理。
let cb = $.Callbacks();
function a (x, y) {
console.log('a', x, y);
}
function b (x, y) {
console.log('b', x, y);
}
cb.add(a, b);
cb.fire(10, 20);
包括后面出现的 deferer,也是使用 Callbacks 管理回调。
function waitHandle () {
var dtd = $.Deferred();
var wait = function () {
var task = function () {
dtd.resolve();
}
setTimeout(task, 2000);
returen dtd;
}
return wait(dtd);
}
var w = waitHandle();
w
.done(function () {
console.log('ok')
})
.fail(function () {
console.log('error');
});
w
.then(
function () {
},
function () {
}
)
w.reject(); // 异步失败后,全部都是失败。可以在外层显示调用reject方法。
jQuery 中处理异步时,前期使用的是 Callbacks,后面又出现 Deferred。
而 Deferred 就是 Promise 最初的形式,目前我们解决回调地狱的手段就是 Promise。
3. ES6中的Promise
promise 是一种规范,不止 jQuery 实现了 promise,还有很多库也实现了 promise,都遵循 promise A+ 规范。例如 bluebird 就是使用promiseA+规范定义的工具包。
先看一个Promise的使用方式。
// executor 执行者
new Promise(function (resolve, reject) {
console.log('promise');
});
console.log(1);
// promise
// 1
从上面的代码中可以看到,传入 Promise 对象中的函数是与外部的打印是同步执行的。
实例化的 Promise 存放以后才会结束的事件,可以被当作一个容器。
(1)promise 的三种状态
1. pending 进行中
2. fufilled(resolve)已成功
3. reject 已失效
(2)promise 的状态转换
promise 异步操作的状态是不受外界影响的,同时,promise 的状态是不可逆的,一旦状态发生变化,不会再改变。promise 的状态固化之后,再对 promsie 对象添加回调函数,可以直接拿到结果,如果是事件的话,一旦错过,就真的错过了。
pending -> resolve
pending -> reject
(3)如何使用 promise
promise 通过函数调用的方式来改变自身状态。
new Promise(function (resolve, reject) {
console.log('promise');
reject();
});
promise 存在两种使用方式。
第一种:
使用 .then 的方式,.catch 的方式。
const promise = new Promise(function (resolve, reject) {
setTimeout(function () {
Math.random() * 100 > 60 ? resolve()
: reject();
}, 300)
});
promise
.then(res => {
console.log('函数执行完成');
})
.catch(err => {
console.log('函数执行失败');
});
promise.then(
() => {
console.log('函数执行完成');
},
() => {
console.log('函数执行失败');
}
);
第二种:
只使用 .then,在 then 属性中传入两个回调函数。
const promise = new Promise((resolve, reject) => {
console.log(0);
resolve(1);
});
promise.then(
(val) => {
console.log(val);
},
(reason) => {
console.log(reason);
}
);
promise 的 resolve 和 reject 是异步操作,调用的是异步的回调函数。
(4)关于 setTimeout 与 promise
setTimeout(() => {
console.log('time');
});
const promise = new Promise((resolve, reject) => {
console.log(0);
resolve(1);
});
promise.then(
(val) => {
console.log(val);
},
(reason) => {
console.log(reason);
}
);
console.log(2);
// 0
// 2
// 1
// time
上面打印的结果是 0 2 1 time。
由此也不得不说一下,JS代码中存在两种任务,宏任务和微任务。
微任务:
promsie、process.nextTick...
宏任务:
setTimeout、setInternal...
JS执行时,存在宏任务队列和微任务队列,每一次事件轮询时,当主线程任务完成后,会调用任务队列中的回调函数推入到执行栈中。优先放入微任务队列中的回调函数,然后再处理宏任务队列中的回调函数。
再来看下面一段代码。
Promise.resolve().then(
() => {
console.log('promise1');
setTimeout(() => {
console.log('setTimeout2')
})
}
);
setTimeout(() => {
console.log('setTimeout1');
Promise.resolve().then(() => {
console.log('promise2');
});
});
// promise1
// setTimeout1
// promise2
// setTimeout2
// promise1
// setTimeout1
// setTimeout2
// promise2
这段代码会出现两种运行结果,这和事件轮询有关。宏任务和微任务是针对异步函数进行划分的,对于同步代码来说,并不存在这两种概念。
如何想了解更多的相关概念,可以去看我之前写的一篇博文《剖析JS执行机制》,在这里就不反复解读了。
(5)promise 的链式调用问题
promise 链式调用时,第二个调用的函数的形参是上次 then 函数返回的值。
即第一次 then 的返回值作为下一次 then 的执行参数。
let promise = new Promise((resolve, reject) => {
setTimeout(() => {
Math.random() * 100 > 60 ? resolve('ok')
: reject('no');
});
});
promise
.then(
(val) => {
console.log(val);
},
(reason) => {
console.log(reason);
}
)
.then(
(val) => {
console.log('second', val);
},
(reason) => {
console.log('second', reason);
}
)
// ok
// second undefined
// no
// second no
let promise = new Promise((resolve, reject) => {
resolve(1);
});
promise
.then(
(val) => {
console.log(val);
return 2;
}
)
.then(
(val) => {
console.log('second', val);
}
);
// 1
// second 2
在链式调用中,then 函数中可以继续返回 promise 对象。
let promise = new Promise((resolve, reject) => {
resolve(1);
});
promise
.then(
(val) => {
console.log(val);
return new Promise((resolve, reject) => {
reject(2);
});
}
)
.then(
(val) => {
console.log('success', val);
},
(err) => {
console.log('error', err);
}
);
// 1
// error 2
4. 总结
本篇文章介绍了 promsie 的由来,以及如何简单的使用 promise。
后面将对 promise 进行进一步的介绍,以及自己实现一个 Promise 对象。