JavaScript Promise
知乎上有一个黑 JavaScript 的段子,大概是说:
N 年后,外星人截获了 NASA 发射的飞行器并破解其源代码,翻到最后发现好几页的 }}}}}}……
这是因为 NASA 近年发射过使用 JavaScript 编程的飞行器,而 Node.js 环境下的 JavaScript 有个臭名昭著的特色:Callback hell(回调地狱的意思)
JavaScript Promise 是一种用来取代超长回调嵌套编程风格(特指 Node.js)的解决方案。
比如:
getAsync("/api/something", (error, result) => {
if(error){
//error
}
//success
});
将可以写作:
let promise = getAsyncPromise("/api/something");
promise.then((result) => {
//success
}).catch((error) => {
//error
});
乍一看好像并没有什么区别,依然是回调。但最近在做的一个东西让我明白,Promise 的目的不是为了干掉回调函数,而是为了干掉嵌套回调函数。
定义 🔗
MDN 定义:
The Promise object is used for asynchronous computations. A Promise represents a value which may be available now, or in the future, or never.
意思大概就是,Promise 是专门用于异步处理的对象。一个 Promise 代表着一个值,这个值可能已经获得了,又可能在将来的某个时刻会获得,又或者永远都无法获得。
简单地说,Promise 对象就是值的代理。经纪人。
简单用法 🔗
创建一个 Promise:
let promise = new Promise((resolve, reject) => {
//success -> resolve(data)
//error -> reject(data)
});
使用 new Promise
来创建 Promise 对象,构造器中传入一个函数,同时对该函数传入 resolve
和 reject
参数,分别代表异步处理成功与失败时将要调用的方法。
处理 Promise 结果:
promise.then(onFulfilled, onRejected)
使用 then
方法来注册结果函数,共可以注册两个函数,其中 onFulfilled
代表成功,后者代表失败。两个参数都是可选参数。
不过,对于失败处理,更加推荐的方式是使用 catch
方法:
promise.catch(onRejected)
这两个方法可以进行链式操作。组合示例:
function asyncFunction() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Async Hello world');
}, 1000);
});
}
asyncFunction()
.then((value) => {
console.log(value); //Async Hello world
})
.catch((error) => {
console.log(error);
});
这里使用了定时器来模拟异步过程,实际上其它异步过程(如 XHR)也大概都是这么个写法。
状态 🔗
Promise 对象共有三种状态:
Fulfilled (成功) Rejected (失败) Pending (处理中)有两条转换路径:
Pending -> Fulfilled ->then
call
Pending -> Rejected -> catch
call
Promise 对象的状态,从 Pending 转换为 Fulfilled 或 Rejected 之后, then
方法或者 catch
方法就会被立即调用,并且这个 promise 对象的状态不会再发生任何变化。也就是说,调用且只调用一次。
链式操作 🔗
链式操作是 Promise 对象的一大亮点。
本节引用一些 Promise Book 的内容。
例如:
function taskA() {
console.log("Task A");
}
function taskB() {
console.log("Task B");
}
function onRejected(error) {
console.log("Catch Error: A or B", error);
}
function finalTask() {
console.log("Final Task");
}
var promise = Promise.resolve();
promise
.then(taskA)
.then(taskB)
.catch(onRejected)
.then(finalTask);
//Task A
//Task B
//Final Task
该代码块实际流程如图所示:
可以看到,这个 onRejected 并不仅仅是 TaskB 的失败处理函数,同时它也是 TaskA 的失败处理函数。而且当 TaskA 失败(reject 被调用或者抛出异常)时,TaskB 将不会被调用,直接进入失败处理。熟悉 express 的玩家应该能看出来了,这简直就和中间件一模一样嘛。
比如说,TaskA 出现异常:
function taskA() {
console.log("Task A");
throw new Error("throw Error @ Task A")
}
function taskB() {
console.log("Task B");// 不会被调用
}
function onRejected(error) {
console.log(error);// => "throw Error @ Task A"
}
function finalTask() {
console.log("Final Task");
}
var promise = Promise.resolve();
promise
.then(taskA)
.then(taskB)
.catch(onRejected)
.then(finalTask);
这里的输出应该就是:
//Task A
//Error: throw Error @ Task A
//Final Task
需要注意的是,如果在 onRejected
或 finalTask
中出现异常,那么这个异常将不会再被捕捉到。因为并没有再继续注册 catch
函数。
借助 Promise 链式操作的特点,复杂的 JavaScript 回调简化将不再是梦。
递归 🔗
Promise 可以实现递归调用,在用来一次性抓取所有分页内容的时候有用。例:
function get(url, p) {
return $.get(url + "?page=" + p)
.then(function(data) {
if(!data.list.length) {
return [];
}
return get(url, p+1)
.then(function(nextList) {
return [].concat(data.list, nextList);
});
});
}
get("urlurl", 1).then(function(list) {
console.log(list);//your full list is here
});
实用方法 🔗
Promise.all 🔗
Promise.all
接受一个 promise 对象的数组作为参数,当这个数组里的所有promise对象全部变为 resolve 或 reject 状态的时候,它才会去调用 then
方法。
例:
function taskA() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('TaskA resolved!');
resolve();
}, 1000);
});
}
function taskB() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('TaskB resolved!');
resolve();
}, 2000);
});
}
function main() {
return Promise.all([taskA(), taskB()]);
}
main()
.then((value) => {
console.log('All resolved!');
})
.catch((error) => {
console.log(error);
});
//TaskA resolved!
//TaskB resolved!
//All resolved!
Promise.race 🔗
跟 Promise.all
类似,略有区别,从名字就能看出来,只要有一个 Task 执行完毕,整个 Promise 就会返回。但是需要注意的是,返回以后并不会取消其它未完成的 Promise 的执行。
function taskA() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('TaskA resolved!');
resolve();
}, 1000);
});
}
function taskB() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('TaskB resolved!');
resolve();
}, 2000);
});
}
function main() {
return Promise.race([taskA(), taskB()]);
}
main()
.then((value) => {
console.log('All resolved!');
})
.catch((error) => {
console.log(error);
});
//TaskA resolved!
//All resolved!
//TaskB resolved!
支持性 🔗
由于是 ES6 语法,目前在浏览器端支持不是特别好,很多移动端浏览器以及 IE 家族均不支持(具体可查看 MDN)。如果要在浏览器端使用需要借助 Babel 编译器。
至于 Node.js 环境则毫无问题。
文章来源:
Author:wxsm's blog
link:https://blog.wxsm.space/p/javascript-promise