Hi I’m Shendi
Promise 是 ECMAScript 6 提供的类,ES6新增,目的是更加优雅地书写复杂的异步任务
大部分浏览器都支持,一些旧的浏览器不支持
简介
在浏览器中,程序的运行是同步的(单线程),要想实现异步,可以使用 setTimeout 函数,AJAX也可以异步执行
当需求很复杂的时候,例如制作一个动画效果,两秒内将元素往上移50像素,然后一秒内往左移动10像素,代码如下
var ele = document.getElementById("id");
// 计算一次多少毫秒,两秒50,一秒10
var oneNum = 2000/50, twoNum = 1000/10;
var num = 0, num2 = 0;
// 两秒移动50像素
function animOne() {
setTimeout(function () {
ele.style.top -= 1;
num++;
if (num == oneNum) {
animTwo();
} else {
anim();
}
}, oneNum);
}
// 一秒往左移动10像素
function animTwo() {
setTimeout(function () {
ele.style.left -= 1;
num2++;
if (num2 == twoNum) {
// 动画完成
} else {
animTwo();
}
}, twoNum);
}
animOne();
上面的代码看起来没有什么太大的问题,但是如果需求更改,在之前基础上又往下移动,则要在 animTwo 执行完成后调用 animThree(往下移动),如果需求又更改,增加更多操作
我们的解决办法大概是将操作完成后直接指定函数调用的形式改为传递回调参数调用,例如
// 两秒移动50像素
function animOne(callback) {
setTimeout(function () {
ele.style.top -= 1;
num++;
if (num == oneNum) {
if (callback) callback();
} else {
anim();
}
}, oneNum);
}
// 一秒往左移动10像素
function animTwo(callback) {
setTimeout(function () {
ele.style.left -= 1;
num2++;
if (num2 == twoNum) {
if (callback) callback();
} else {
animTwo();
}
}, twoNum);
}
function animThree() {
...}
animOne(animTwo(animThree));
这种异步回调的方式可以使用 Promise 替代,目的是更加优雅地书写复杂的异步任务
可以理解为一种规范
优势
更加灵活
在传统回调中,调用时就需要传递回调函数,例如
animOne(aniTwo());
而在Promise中,可以在任意时候指定(在异步执行完成也可以,依然会被调用)
支持链式调用,解决回调地狱
回调地狱是指过度使用回调方法嵌套的一种情况,让代码看起来非常深、难以维护和理解。以下是一个示例:
function getDataFromServer(url, callback) {
var httpRequest = new XMLHttpRequest();
httpRequest.onreadystatechange = function() {
if (httpRequest.readyState === 4 && httpRequest.status === 200) {
var responseData = JSON.parse(httpRequest.responseText);
callback(responseData);
}
};
httpRequest.open('GET', url, true);
httpRequest.send();
}
getDataFromServer('https://example.com/data1', function(response1) {
getDataFromServer('https://example.com/data2/' + response1.id, function(response2) {
getDataFromServer('https://example.com/data3/' + response2.id, function(response3) {
// do something with the data
});
});
});
在上例中,三个嵌套的回调可能会变得非常深,这使得代码难以理解和维护。为了解决回调地狱问题,可以使用Promise 或async/await等技术。
Promise
通过 new Promise 来创建一个对象,构造函数有一个参数,为函数,此函数也有两个参数,通常命名为resolve和reject,也是函数
其中当函数执行成功时,应调用resolve,执行失败时应调用reject,可以自行定义这两个函数的参数
例如
var p = new Promise(function (resolve, reject) {
if (true) {
// 成功
resolve("成功了");
} else {
// 失败
reject("失败了");
}
});
resolve 和 reject 可以理解为回调函数
Promise本身不是异步的,而是用来包含执行异步操作的
创建Promise传递的函数会直接执行(同步)
Promise对象有以下几个函数
名称 | 描述 |
---|---|
then | 函数,成功后会执行,返回promise对象 |
catch | 函数,失败后会执行,返回promise对象 |
finally | 函数,无论成功失败都执行,返回promise对象 |
例如
p.then(function (val) {
console.log(val);
}).catch(function (val) {
console.log(val);
});
三种状态
Promise的三种状态分别为:
- Pending(进行中):Promise对象创建时就处于Pending状态,表示异步操作正在进行中,尚未完成。
- Fulfilled(已完成):异步操作成功完成,Promise对象从Pending状态转变为Fulfilled状态,同时返回一个结果值。
- Rejected(已失败):异步操作出现错误或异常,Promise对象从Pending状态转变为Rejected状态,同时返回一个错误信息。
在Promise的生命周期中,状态只会由Pending变为Fulfilled或Rejected。一旦状态转变为Fulfilled或Rejected,就会保持不变,直到Promise对象被释放。
当状态转变为Fulfilled或Rejected时候,会执行对应的回调函数,且有多少个执行多少个,例如
p.then(function () {
console.log(1);
}).then(function () {
console.log(2);
}).then(function () {
console.log(3);
});
会按顺序执行输出 123
catch链式调用的话只有第一个会被执行
链式调用按顺序执行,then可以传递函数参数,处理then有任何异常都会直接执行catch
var p = new Promise(function (resolve, reject) {
setTimeout(function () {
console.log(1);
resolve(2);
}, 2000);
});
p.then(function (val) {
console.log(val);
return 3;
}).then(function (val) {
console.log(val);
}).finally(function () {
console.log("end");
}).then(function (val) {
console.log(val);
throw "4 error";
}).catch(function (val) {
console.log(val);
});
异步函数
异步函数(async function)是 ECMAScript 2017 (ECMA-262) 标准的规范,几乎被所有浏览器所支持,除了 Internet Explorer。
异步函数是一种 JavaScript 函数类型,它允许在执行过程中不阻塞代码的执行,而是在后台继续进行其他操作。异步函数通常使用回调函数、Promise 或 async/await 等方法来处理异步操作。
在函数前加上 async
修饰代表异步函数,异步函数整体异步执行
异步函数内可以使用 await 等待 promise 结果,例如
function showText(text, delay) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
console.log(text);
resolve();
}, delay);
});
}
async function test() {
await showText("一秒过去了", 1000);
await showText("两秒过去了", 1000);
await showText("五秒过去了", 3000);
}
test();
原理与 Promise 原生 API 的机制是一模一样的,只不过更便于程序员阅读
异步函数内需要用try catch捕获异常
如果promise有正常的返回值,可以直接赋值
async function test() {
try {
await new Promise(function (resolve, reject) {
reject("错误");
// 或者抛出错误
// throw "错误";
});
} catch (e) {
console.log(e); }
await new Promise(function (resolve, reject) {
resolve("返回值");
});
}
END