Promise 的用途
同步
- 直接能拿到结果
异步
- 不能直接拿到结果
以AJAX的封装为例
ajax = (method, url, options) => {
const{success, fail} = options
const request = new XMLHttpRequest()
request.open(method, url)
request.onreadystatechange = () => {
if (request.readyState === 4) {
if (request.status < 400) {
success.call(null, request.response)
} else if (request.Status >= 400) {
fail.call(null, request, request.status)
}
}
}
request.send()
}
ajax('get', '/xxx', {
success(reponse){}, fail:(request, status) => {}
})
request.send()之后,并不能直接得到response- 必须等到
readyState变为4后,浏览器回头调用request.onreadystatechange函数,我们才能得到requst.reponse
回调
- 写给自己用的函数,不是回调
- 写给别人用的函数,就是回调
request.onreadystatechange就是我写给浏览器调用的- 意思就是你浏览器回头调用一下这个函数
- 回头调用 => 回头请你 写了却不调用,给别人调用的函数
回调和异步的关系
关联
- 异步任务需要在得到结果时通知JS来拿结果
- 怎么通知?
- 可以让JS留一个函数地址(电话号码)给浏览器
- 异步任务完成时浏览器调用该函数地址即可(拨打电话)
- 同时把结果作为参数传给该函数(电话里说可以来吃了)
- 这个函数是我写给浏览器调用的,所以时回调函数 区别
- 异步任务需要用到回调函数来通知结果
- 但回调函数不一定只用在异步任务里
- 回调可以用到同步任务里
array.forEach(n => console.log(n))就是同步回调
如何知道一个函数是同步还是异步
如果一个函数的返回值处于
- setTimeout
- AJAX(既 XMLHttpRequest)
- AddEventListener
这三个东西内部,那么这个函数就是异步函数
AJAX可以设置同步的???如果设置成同步的那么会导致请求期间页面卡住
如果异步任务两个结果成功或者失败呢?
两个结果
- 回调接受两个参数
fs.readFile('./1.txt', (error, data) => {
if(error) {
console.log('失败');
return
}
console.log(data.toString()) // 成功
})
- 使用1两个回调
ajax('get', '/1.json', data() => {}, error() => {})
// 前面函数是成功回调,后面函数是失败回调
ajax('get', '/1.json', {
success: ()={}, fail: ()=>{}
})
// 接受对象,对象有两个key表示成功和失败
不足之处
- 不规范,名称五花八门,有人用
success + error,有人用success + fail,有人用done + fail - 容易出现回调地狱,代码变得看不懂
- 很难进行错误处理 回调地狱举例:
getUser(user =>{
getGroup(user, (groups) => {
groups.forEach((g) => {
g.filter(x => x.ownerId === user.id)
.forEach(x => console.log(x))
})
})
})
那么该如何解决呢?
- 规范回调的名字或者顺序
- 拒绝回调地狱,让代码可读性更强
- 很方便地捕获错误
其实
早在1976年,Daniel P.Friedman和David wose,两人就提出了Promise思想,后人基于此发明了Future、Delay、Deferred
前端结合 Promise 和 JS,制定了Promise/A+规范 该规范详细描述了Promise的原理和方法
我们还是以AJAX的封装为例
ajax = (method, url, options) => {
const{success, fail} = options
const request = new XMLHttpRequest()
request.open(method, url)
request.onreadystatechange = () => {
if (request.readyState === 4) {
if (request.status < 400) {
success.call(null, request.response)
} else if (request.Status >= 400) {
fail.call(null, request, request.status)
}
}
}
request.send()
}
ajax('get', '/xxx', {
success(reponse){}, fail:(request, status) => {}
})
改成Promise写法
// 先改调用的姿势
ajax('get', '/xxx', {
success(response){}, fail: (request, status) => {}
})
// 上面用了两个回调,还是用了 success 和 fail
// 改成Promise写法
ajax('get', '/xxx')
.then(response) => {}, (requset) => {})
虽然也是回调,但是不需要记 success 和 fail
then 的第一个参数就是 success
then 的第二个参数就是 fail
请问 ajax 返回了什么???
- 返回了一个含有
.then()方法的对象 如何得到? - 得要改造ajax源码
ajax = (method, url, options) => {
return new Promise ((resolve, reject) => {
const{success, fail} = options
const request = new XMLHttpRequest()
request.open(method, url)
request.onreadystatechange = () => {
if (request.readyState === 4) {
if (request.status < 400) {
resolve.call(null, request.response)
} else if (request.Status >= 400) {
reject.call(null, request)
}
}
}
request.send()
})
}
ajax('get', '/xxx')
.then(response) => {}, (requset) => {})
如何创建一个 new Promise
return new Promise((resolve, reject) => {})
如何使用 Promise.prototype.then
then() 方法返回一个 Promise (en-US)。它最多需要有两个参数:Promise 的成功和失败情况的回调函数。
p.then(onFulfilled[, onRejected]);
p.then(value => {
// fulfillment
}, reason => {
// rejection
});
-
onFulfilled可选 -
当 Promise 变成接受状态(fulfilled)时调用的
函数。该函数有一个参数,即接受的最终结果(the fulfillment value)。如果该参数不是函数,则会在内部被替换为(x) => x,即原样返回 promise 最终结果的函数 -
onRejected可选 -
当 Promise 变成拒绝状态(rejected)时调用的
函数。该函数有一个参数,即拒绝的原因(rejection reason)。 如果该参数不是函数,则会在内部被替换为一个 "Thrower" 函数 (it throws an error it received as argument)。
返回值
当一个 Promise 完成(fulfilled)或者失败(rejected)时,返回函数将被异步调用(由当前的线程循环来调度完成)。具体的返回值依据以下规则返回。如果 then 中的回调函数:
- 返回了一个值,那么
then返回的 Promise 将会成为接受状态,并且将返回的值作为接受状态的回调函数的参数值。 - 没有返回任何值,那么
then返回的 Promise 将会成为接受状态,并且该接受状态的回调函数的参数值为undefined。 - 抛出一个错误,那么
then返回的 Promise 将会成为拒绝状态,并且将抛出的错误作为拒绝状态的回调函数的参数值。 - 返回一个已经是接受状态的 Promise,那么
then返回的 Promise 也会成为接受状态,并且将那个 Promise 的接受状态的回调函数的参数值作为该被返回的Promise的接受状态回调函数的参数值。 - 返回一个已经是拒绝状态的 Promise,那么
then返回的 Promise 也会成为拒绝状态,并且将那个 Promise 的拒绝状态的回调函数的参数值作为该被返回的Promise的拒绝状态回调函数的参数值。 - 返回一个未定状态(
pending)的 Promise,那么then返回 Promise 的状态也是未定的,并且它的终态与那个 Promise 的终态相同;同时,它变为终态时调用的回调函数参数与那个 Promise 变为终态时的回调函数的参数是相同的。
如何使用 Promise.all
Promise.all可以将多个Promise实例包装成一个新的Promise实例。同时,成功和失败的返回值是不同的,成功的时候返回的是一个结果数组,而失败的时候则返回最先被reject失败状态的值。
Promise.all 等待所有都完成(或第一个失败)。
var p1 = Promise.resolve(3);
var p2 = 1337;
var p3 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, 'foo');
});
Promise.all([p1, p2, p3]).then(values => {
console.log(values); // [3, 1337, "foo"]
});
如果参数中包含非 promise 值,这些值将被忽略,但仍然会被放在返回数组中(如果 promise 完成的话)
// this will be counted as if the iterable passed is empty, so it gets fulfilled
var p = Promise.all([1,2,3]);
// this will be counted as if the iterable passed contains only the resolved promise with value "444", so it gets fulfilled
var p2 = Promise.all([1,2,3, Promise.resolve(444)]);
// this will be counted as if the iterable passed contains only the rejected promise with value "555", so it gets rejected
var p3 = Promise.all([1,2,3, Promise.reject(555)]);
// using setTimeout we can execute code after the stack is empty
setTimeout(function(){
console.log(p);
console.log(p2);
console.log(p3);
});
// logs
// Promise { <state>: "fulfilled", <value>: Array[3] }
// Promise { <state>: "fulfilled", <value>: Array[4] }
// Promise { <state>: "rejected", <reason>: 555 }
// we are passing as argument an array of promises that are already resolved,
// to trigger Promise.all as soon as possible
var resolvedPromisesArray = [Promise.resolve(33), Promise.resolve(44)];
var p = Promise.all(resolvedPromisesArray);
// immediately logging the value of p
console.log(p);
// using setTimeout we can execute code after the stack is empty
setTimeout(function(){
console.log('the stack is now empty');
console.log(p);
});
// logs, in order:
// Promise { <state>: "pending" }
// the stack is now empty
// Promise { <state>: "fulfilled", <value>: Array[2] }
var p1 = new Promise((resolve, reject) => {
setTimeout(resolve, 1000, 'one');
});
var p2 = new Promise((resolve, reject) => {
setTimeout(resolve, 2000, 'two');
});
var p3 = new Promise((resolve, reject) => {
setTimeout(resolve, 3000, 'three');
});
var p4 = new Promise((resolve, reject) => {
setTimeout(resolve, 4000, 'four');
});
var p5 = new Promise((resolve, reject) => {
reject('reject');
});
Promise.all([p1, p2, p3, p4, p5]).then(values => {
console.log(values);
}, reason => {
console.log(reason)
});
//From console:
//"reject"
//You can also use .catch
Promise.all([p1, p2, p3, p4, p5]).then(values => {
console.log(values);
}).catch(reason => {
console.log(reason)
});
//From console:
//"reject"
如何使用 Promise.race
Promise.race(iterable) 方法返回一个 promise,一旦迭代器中的某个promise解决或拒绝,返回的 promise就会解决或拒绝。
race 函数返回一个 Promise,它将与第一个传递的 promise 相同的完成方式被完成。它可以是完成( resolves),也可以是失败(rejects),这要取决于第一个完成的方式是两个中的哪个。
如果传的迭代是空的,则返回的 promise 将永远等待。
如果迭代包含一个或多个非承诺值和/或已解决/拒绝的承诺,则 Promise.race 将解析为迭代中找到的第一个值。
也就是说Promse.race就是赛跑的意思,Promise.race([p1, p2])里面哪个结果获得的快,就返回那个结果,不管结果本身是成功状态还是失败状态
let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('success')
},1000)
})
let p2 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('failed')
}, 500)
})
Promise.race([p1, p2]).then((result) => {
console.log(result)
}).catch((error) => {
console.log(error) // 打开的是 'failed'
})