Promise为什么比回调函数更好
Promise是什么
promise是什么, promise是一种为你等待底层异步值完成的机制,同时promise也是一种异步任务流程控制的机制,以下举例说明这两种理解
Promise等待了未来值
我们先看一个例子, 假设我们有两个函数 fetchX, fetchY,我们需要把这两个函数的结果相加
let x, y;
fetchX(function (x) {
x = x;
});
fetchY(function (Y) {
y = y;
});
console.log(x + y);
在上述中,console.log(x + y);的结果是不确定的,当x已经决议(完成),但是y还没有决议,或者是y已经决议,但是x还没有决议,又或者是两者都没有决议,那么我们就完全得不到想要的结果,怎么去解决这个问题呢,我们可以写一个函数去解决这个问题
function add (fetchX, fetchY, cb) {
let x, y;
fetchX(function (valX) {
x = valX;
if (y !== undefined) { //y准备好了?
cb(x + y);
}
});
fetchY(function (valY) {
y = valY;
if (x !== undefined) {//x准备好了?
cb(x + y);
}
});
}
//调用add
add(fetchX, fetchY, function (sum) {
console.log(sum);
})
在这段代码中,我们使用了一个函数add,这个函数它为我们等待了x或者y决议,并且在全部完成决议的时刻执行打印,这个函数把现在和将来的结果都统一了,它不在会有undefined的成分了,同时你可以发现,我们把console.log的代码放到了将来执行,也就是说,这个函数把现在的代码(指打印操作)和将来的代码(指赋值操作),全部放到了将来,这个也就是promise的概念,请看以下代码
function add (xPromise, yPromise) {
return Promise.all([xPromise, yPromise])
.then((value) => {
return value[0] + value[1];
})
}
add(fetchX, fetchY)
.then((sum) => {
console.log(sum);
}, (err) => {
console.error(err);
})
这段函数和上述的函数结果是一样的,区别在于上述的代码非常丑陋,所以我们可以说promise是一种为你等待未来值并且把代码全部变成异步的机制
Promise控制异步任务流程
请看以下代码
function foo (x) {
return new Promise((resolve, reject) => {
//最终可能调用resolve(...)或者reject(...)
})
}
function bar () {
//foo完成,执行bar的任务
}
function oopsBar () {
//foo执行出错
}
function baz () {
//foo完成,执行baz的任务
}
function oopsBar() {
//foo执行出错
}
var p = foo(42);
p.then(bar, oopsBar);
p.then(baz, oopsBaz);
在上述代码中,当p执行成功。则会执行函数bar, baz,如果出错,则执行函数oopsBar,oopsBaz,从这个意义上说,foo在成功的时候才会调用bar, baz,出错则调用oppsBar,oppsBaz,不管是什么结果,都是通过Promise控制接下来的流程,,从这个角度上我们说promise是一种控制异步任务流程的机制
Promise为什么解决了回调函数的问题
回调函数在异步流程中的两个问题,一个是回调函数在异步编程中非线性非顺序,一个是回调函数在异步编程中的信任问题。首先,promise写法是线性的,每一个then都不需要关心上一个then的时间状态,当then的回调执行的时候,上一个then的promise必然是已经决议了,这就解决了嵌套带来的问题。 其次是回调函数的信任问题。
调用过早
调用过早主要是指回调函数是否是有时同步有时异步,回调函数如果有时候是同步,有时候又是异步,这样的问题被称为Zalgo,但是promise天生就避免了这个问题,Promise的then总是会被异步调用,即使你的给Promise传入的函数是立即决议的,类似于new Promise(function () { resolve(42) } ), 它的then也会被异步的调用
调用过晚
Promise的对象在完成的时候(Promise对象调用resolve或reject),这个promise的then注册的回调函数就在下一个异步事件点上一定会被触发, 请你判断下面的例子的顺序
p.then(function () {
p.then(funcion () {
console.log("C");
});
console.log("A");
});
p.then(function () {
console.log("B");
});
在这个例子中, C无法打断或者抢占B, 执行顺序一定是B,Promise这样的运行方式保证了B不会被后注册的回调函数执行打印了C导致从调用过晚
回调未调用
Promise的then一定会被调用,这是promise机制本身保证的,如果promise永远没有被完成,也可以使用代码解决
function timeoutPromise(delay) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
reject("Timeout");
}, delay)
})
}
Promise.race([
foo(),
timeoutPromise(3000)
]).then(function () {
//foo..及时完成
}, function () {
//foo拒绝或者未按时完成
})
总结
Promise为什么比回调函数好,一点在于可读性,另一点在于Promise把回调函数的控制反转再反转,通过Promise去执行我们的回调,所以Promise解决信任问题的方法可以说是把控制权给了一个绝对可信任的系统(Promise),而不是第三方库