前端开辟中常常会举行一些异步操纵,罕见的异步有:
- 收集要求:ajax
- IO操纵: readFile
- 定时器:setTimeout
回调
最基础的异步解决方案莫过于回调函数了
前端常常会在胜利时和失利时离别注册回调函数
const req = new XMLHttpRequest();
req.open('GET', URL, true);
req.onload = function () {
// 胜利的回调
if (req.status === 200) {
console.log(req.statusText)
}
};
req.onerror = function () {
// 失利的回调
console.log(req.statusText)
};
req.send();
node的异步api,则一般只注册一个回调函数,经由过程商定的参数来推断究竟是胜利照样失利:
const fs = require("fs");
fs.readFile('input.txt', function (err, data) {
// 回调函数
// 第一个参数是err,假如有err,则示意挪用失利
if (err) {
return console.error(err);
}
console.log("异步读取: " + data.toString());
});
回调的异步解决方案自身也简朴易懂,然则它有一个致命的瑕玷:没法文雅的掌握异步流程
什么意思?
单个异步固然能够很简朴的运用回调函数,然则关于多个异步操纵,就会堕入回调地狱中
// 要求data1胜利后再要求data2,末了要求data3
const ajax = $.ajax({
url: 'data1.json',
success: function(data1) {
console.log(data1);
$.ajax({
url: 'data2.json',
success: function(data2) {
console.log(data2);
$.ajax({
url: 'data3.json',
success: function(data3) {
console.log(data3);
}
})
}
})
}
})
这类要按递次举行异步流程掌握的场景,回调函数就显得左支右绌了。这时刻,Promise的异步解决方案就被提了出来。
Promise
当初在学Promise时,看得我真是一脸懵逼,完整不明白这货究竟怎样用。实在,Promise的api要分红两部分来明白:
- Promise组织函数:resolve reject (转变内部状况)
- Promise对象: then catch (流程掌握)
Promise对象
Promise对象代表一个异步操纵,有三种状况:pending(举行中)、fulfilled(已胜利)和rejected(已失利)
初始时,该对象状况为pending,以后只能变成fulfilled和rejected个中的一个
then要领有两个参数,离别对应状况为fulfilled和rejected时的回调函数,个中第二个参数可选
promise.then(function(value) {
// success
}, function(error) {
// failure
});
一般我们会省略then的第二个参数,而改用catch来注册状况变成rejected时的回调函数
promise.then(function(value) {
// success
}).catch(function(error) {
// failure
});
Promise组织函数
Promise对象怎样天生的呢?就是经由过程组织函数new出来的。
const promise = new Promise(function(resolve, reject) {
});
Promise组织函数吸收一个函数作为参数,这个函数能够吸收两个参数:resolve和reject
resolve, reject是两个函数,由JavaScript引擎供应,不必本身编写
前面我们说过,Promise对象有三种状况,初始时为pending,以后能够变成fulfilled或许rejected,那怎样转变状况呢?答案就是挪用resolve或许reject
挪用resolve时,状况变成fulfilled,示意异步已完成;挪用reject时,状况变成rejected,示意异步失利。
回折衷Promise的对照
实在这里就是Promise最难明白的处所了,我们先看下例子:
回调函数封装
function getURL(URL, success, error) {
const req = new XMLHttpRequest();
req.open('GET', URL, true);
req.onload = function () {
if (req.status === 200) {
success(req.responseText);
} else {
error(new Error(req.statusText));
}
};
req.onerror = function () {
error(new Error(req.statusText));
};
req.send();
}
const URL = "http://httpbin.org/get";
getURL(URL, function onFulfilled(value) {
console.log(value);
}, function onRejected(error) {
console.error(error);
})
Promise封装
function getURL(URL) {
return new Promise(function (resolve, reject) {
const req = new XMLHttpRequest();
req.open('GET', URL, true);
req.onload = function () {
if (req.status === 200) {
resolve(req.responseText);
} else {
reject(new Error(req.statusText));
}
};
req.onerror = function () {
reject(new Error(req.statusText));
};
req.send();
});
}
const URL = "http://httpbin.org/get";
getURL(URL).then(function onFulfilled(value){
console.log(value);
}).catch(function onRejected(error){
console.error(error);
});
两段代码最大的区分就是:
用回调函数封装的getURL函数,须要显著的传给它胜利和失利的回调函数,success和error的终究挪用是在getURL里被挪用的
用Promise封装的getURL函数,完整不关心胜利和失利的回调函数,它只须要在ajax胜利时挪用resolve(),通知promise对象,你如今的状况变成了fulfilled,在ajax失利时,挪用reject()。而真正的回调函数,是在getURL的表面被挪用的,也就是then和catch中挪用
then要领返回的是一个新的Promise实例(注重,不是本来谁人Promise实例)。因而能够采纳链式写法,即then要领背面再挪用另一个then要领。
function getURL(URL) {
return new Promise(function (resolve, reject) {
const req = new XMLHttpRequest();
req.open('GET', URL, true);
req.onload = function () {
if (req.status === 200) {
resolve(req.responseText);
} else {
reject(new Error(req.statusText));
}
};
req.onerror = function () {
reject(new Error(req.statusText));
};
req.send();
});
}
const URL = "http://httpbin.org/get";
const URL2 = "http://deepred5.com/cors.php?search=ntr";
getURL(URL).then(function onFulfilled(value){
console.log(value);
// 返回了一个新的Promise对象
return getURL(URL2)
}).then(function onFulfilled(value){
console.log(value);
}).catch(function onRejected(error){
console.error(error);
});
这段代码就充分说明了Promise关于流程掌握的上风:读取URL的数据后再读取URL2,没有了之前的回调地狱题目。
Promise运用
Promise常常用于对函数的异步流程封装
function getURL(URL) {
return new Promise(function (resolve, reject) {
const req = new XMLHttpRequest();
req.open('GET', URL, true);
req.onload = function () {
if (req.status === 200) {
resolve(req.responseText);
} else {
reject(new Error(req.statusText));
}
};
req.onerror = function () {
reject(new Error(req.statusText));
};
req.send();
});
}
const preloadImage = function (path) {
return new Promise(function (resolve, reject) {
const image = new Image();
image.onload = resolve;
image.onerror = reject;
image.src = path;
});
};
const fs = require('fs')
const path = require('path')
const readFilePromise = function (fileName) {
return new Promise((resolve, reject) => {
fs.readFile(fileName, (err, data) => {
if (err) {
reject(err)
} else {
resolve(data.toString())
}
})
})
}
连系上面几个例子,我们能够看出Promise封装代码的基础套路:
const methodPromise = function() {
return new Promise((resolve, reject) => {
// 异步流程
if (/* 异步操纵胜利 */){
resolve(value);
} else {
reject(error);
}
})
}
Promise.race Promise.all
Promise.all 吸收一个promise对象的数组作为参数,当这个数组里的一切promise对象悉数变成resolve的时刻,它才会去挪用then要领,假如个中有一个变成rejected,就直接挪用catch要领
传给then要领的是一个数组,内里离别对应promise返回的效果
function getURL(URL) {
return new Promise(function (resolve, reject) {
const req = new XMLHttpRequest();
req.open('GET', URL, true);
req.onload = function () {
if (req.status === 200) {
resolve(req.responseText);
} else {
reject(new Error(req.statusText));
}
};
req.onerror = function () {
reject(new Error(req.statusText));
};
req.send();
});
}
Promise.all([getURL('http://deepred5.com/cors.php?search=ntr'), getURL('http://deepred5.com/cors.php?search=rbq')])
.then((dataArr) => {
const [data1, data2] = dataArr;
}).catch((err) => {
console.log(err)
})
Promise.race相似,只不过只需有一个Promise变成resolve就挪用then要领
Promise.resolve Promise.reject
Promise.resolve(42);
// 等价于
new Promise(function(resolve){
resolve(42);
});
Promise.reject(new Error("出错了"))
// 等价于
new Promise(function(resolve,reject){
reject(new Error("出错了"));
});
Promise.resolve(42).then(function(value){
console.log(value);
});
Promise.reject(new Error("出错了")).catch(function(error){
console.error(error);
});
Promise.resolve要领另一个作用就是将thenable对象转换为promise对象
const promise = Promise.resolve($.ajax('/json/comment.json'));// => promise对象
promise.then(function(value){
console.log(value);
});
thenable对象指的是具有then要领的对象:
let thenable = {
then: function(resolve, reject) {
resolve(42);
}
};
let p1 = Promise.resolve(thenable);
p1.then(function(value) {
console.log(value); // 42
});
非常捕捉
抱负状况下,Promise能够经由过程catch捕捉到非常,然则假如我们没有运用catch,那末虽然掌握台会打印毛病,然则此次毛病并不会停止剧本实行
<script>
const a = b.c.d;
console.log(1); // 代码报错,不会运转到此处
</script>
<script>
console.log(2); // 代码运转
</script>
上述代码只会打印2
<script>
const promise = new Promise((resolve, reject) => {
const a = b.c.d;
resolve('ok');
})
promise.then(data => {
console.log(data)
})
console.log(1); // 代码报错,然则会运转到此处
</script>
<script>
console.log(2); // 代码运转
</script>
打印1和2
解决要领:
window有一个unhandledRejection事宜,特地监听未捕捉的reject毛病
window.onunhandledrejection = function(e) {
console.log(e.reason);
}
const promise = new Promise((resolve, reject) => {
const a = b.c.d;
resolve('ok');
})
promise.then(data => {
console.log(data)
})