Promise是ES6中一个很重要的异步语法.可以很优雅的解决异步,回调等问题.
首先来引入生成器 generator
看一个同步的例子:从server端获取json数据
try{
// 获取整个项目
let project = syncGetJSON('lorry.com');
// 获取项目中的某一个任务
let mission = syncGetJSON(project[0].missionURL);
// 获取该任务中的具体信息
let missionDetail = syncGetJSON(mission[0].detailURL);
} catch(e) {
console.error(e)
}
上述例子的写法看起来十分的好理解,但也有一个严重的问题是因为写的都是同步代码,所以会阻塞掉整个逻辑的运行.如果project的内容很大,需要10s,那么我们的程序就要等待10s后才能继续运行,用户交互十分的不友好.
所以有了异步
getJSON('lorry.com', (err, project) => {
if (err) {
console.error(err)
return
}
getJSON(project[0].missionURL, (err, mission) => {
if (err) {
console.error(err)
return
}
getJSON(mission.detailURL, (err, detail) => {
if(err) {
console.error(err)
return
}
console.log(detail)
})
})
})
上述代码可以解决之前的阻塞问题,但是是不是看起来十分的不优雅?有许多的err判断,然后一层一层的嵌套(回调地狱 callback hell
)
于是乎,生成器 generator
横空出世,如果能够很清楚的理解下面这段代码,那么你可以快速浏览,或许这些都是你已经知道的知识了.
// getJSON为一个Promise
async(function* () {
try {
const project = yield getJSON('lorry.com')
const mission = yield getJSON(project[0].missionURL)
const detail = yield getJSON(misson.detailURL)
} catch(e) {
console.error(e)
}
})
function async (generator) {
let iterator = generator();
function handle(iteratorResult) {
if (iterator.done) { return }
const iteratorValue = iteratorResult.value
if(iteratorValue instanceof Promise) {
iteratorValue.then(res => handle(iterator.next(res))).catch(err => iterator.throw(err))
}
}
}
try {
handle(iterator.next())
} catch(e) {
iterator.throw(e)
}
是不是跟第一个的写法很像?
generator其实就两个东西,*
和 yield
1 for-of可以遍历所有的yield
function* example() {
yield(1)
yield(2)
yield(3)
}
for (let num of example()) {
console.log(num) // 打印1,2,3
}
2 使用next()对generator进行控制, .value表示yield的值.done属性表示终止条件
let nums = example() // nums为一个迭代器(iterator)
let num1 = nums.next().value // 1
let num2 = nums.next().value // 2
let num3 = nums.next().value // 3
let num4 = nums.next().value // undefined
console.log(nums.done) // true
现在可以用while来迭代了,其实for-of的就是以下while的一个语法糖.
while(!(item = nums.next()).done) {
console.log(item // 1,2,3
}
3 generator之间的嵌套 yield
表示另一个生成器,会按顺序将嵌套生成器的迭代完,再往下执行
function* example2() {
yield* example1();
yield 4;
yield 5;
}
for (let num of example2) {
console.log(num) //1,2,3,4,5
}
4 可以在generator中写while(true)
function* count() {
let id = 1;
while(true) {
yield id++
}
}
let idIterator = count()
idIterator.next().value // 1
idIterator.next().value // 2
// 不使用生成器的实现方式,会造成变量的污染
function() {
let id = 1; // 我本不属于这里.
function count() {
return id++
}
console.log(count())
}
这个生成器永远不会结束,.done
永远为false
5 使用generator来遍历树,其中也可以看出如何使用生成器的递归调用
function* DomTraversal(element) {
yield element;
element = element.firstElementChild()
while (element) {
yield* DomTraversal(element) // 先深度遍历
element = element.nextElementSibling; // 再广度遍历
}
}
const head = document.getElementById('head')
for (let element of DomTraversal(head)) {
console.log(element)
}
6 如何与generator进行通信? 将值传入next参数中
function* example3(num) {
const num1 = yield (num + 1)
yield (num + num1 + 1)
}
let nums = example3(1)
let num1 = nums.next().value // 2
let num2 = nums.next(2).value // 4
这样就可以与generator进行双向通信, yield 从generator中传出, next(data) 将data传入到generator中.
但是要注意的是,无法对第一个next进行通信.
因为需要通过变量来保存传给generator
的数据,然后再传到yield
中,如上例中使用num1
保存传入数据,再传到下一个yield
语句中.由于第一个next
(yield
)没有变量可以储存.
不过可以通过传参的方式当成initial value
传入,如上例的num
7 generator抛出错误
function* example4(name) {
try{
yield (`my name is ${name}`)
fail('坦白从宽,抗拒从严')
} catch (e) {
console.log(e)
}
}
let iterator = example4('lorry')
const result = iterator.next().value // my name is lorry
iterator.throw('我主动认错') // 打印错误信息.
8 generator的本质
- 在执行generator的时候并没有执行它,而是创建了一个新的用于向生成器请求值的迭代器(iterator), generato yield一个值,然后suspend,等待下一次的next执行.
- 包括4个状态
- 1 suspended start –> 当generator创建时,此时没有任何generator的代码被执行
- 2 executing –> 当next调用时
- 3 suspended yield –> 执行时,到达yield语句后,会创建一个新对象(eg.
{value: 1,done: false}
),generator暂停并等待 - 4 complete 当yield执行完毕的下一个next()或者显式return时 返回
{value: undefined, done:true}
9 注意
return
在generator
中的使用function* example5() { yield 1 return 2 } function* example6() { yield 1 yield 2 } let a = example5() for(let i of a ) { console.log(i) // 1,只有一个1 } let b = example6() for(let i of b) { console.log(i)// 1,2 }
接下来就是Promise
1 先抛一个例子
const smallPromise = new Promise((resolve, reject) => {
resolve('yes')
// reject('an error happen to reject')
})
smallPromise.then(res => console.log(res), err => console.log(err))// yes
2 如何实现异步的功能?
const asyncPromise = new Promise((resolve,reject) => {
console.log('asyncPromise create')
setTimeout(()=>{
console.log('asyncPromise callback')
resolve('async solving')
console.log('async solved')
},500)
})
const immediatePromise = new Promise((resolve,reject) => {
console.log('immediatePromise create')
resolve('immediate solving')
console.log('immediatePromise solved')
})
asyncPromise.then(res => console.log(res))
immediatePromise.then(res => console.log(res))
console.log('end code')
/**
asyncPromise create
immediatePromise create
immediatePromise solved
end code
immediate solving
asyncPromise callback
async solved
async solving
*/
可以看到then之后的语句是在执行完所有的同步代码之后执行(注意end code
的位置)
3 明目张胆的拒绝(reject)
const promise = new Promise((resolve,reject) => {
reject('我不想给你任何承诺')
})
promise.then(
() => console.log('我愿意'),
(err) => console.log(err)
);// 我不想给你任何承诺
除此之外还有catch
的内建语法
promise
.then(() => console.log('我愿意'))
.catch((err) => {
console.log(err);
})// 我不想给你任何承诺
4 偷偷摸摸的拒绝
const promise = new Promise((resolve, reject) => {
undeclareVarible++;
});
promise.then(()=>console.log('我愿意')).catch(() => console.log('其实我内心是拒绝的'));
注意,不管是拒绝还是接收都是一个最终的确定态,promise会由pending转为resolved
5 实际的例子:
function getJSON(url) {
return new Promise((resolve, reject) => {
const request = new XMLHttpRequest();
request.open('get',url);
// 如果服务器返回了消息,但是并不代表是我们预期的
request.onload = function() {
try {
if (this.status === 200) {
resolve(JSON.parse(this.response))
} else {
reject(this.status + ':' + this.statusText);
}
} catch (e) {
reject(e.message);
}
}
// 如果与服务器通信出现了问题
request.onerror = function() {
reject(this.status + ':' this.statusText)
}
request.send()
})
}
getJSON('data/lorry.json').then(res => console.log('lorry got'), err => console.error('something wrong:', err))
6 链式Promise
看了上面的getJSON之后,对本文提出的第一个例子应该就会有不一样的理解了
getJSON('data/lorry.com')
.then(res=>getJSON(res[0].missionURL))
.then(res=>getJSON(res.missionDetaiURL))
.then(res=>console.log(res))
.catch(err=>console.error(err))
是不是清爽很多了?注意,链式操作的时候使用catch更为方便,他会将整条链上的任何错误都catch到,如果使用reject回调,会重复性的写很多错误handle代码
7 Promise.all 和 Promise.race
如果有多个相互之间不引用的Promise可以这么写
Promise.all([getJSON(url1),getJSON(url2),getJSON(url3)].then(results => {
const res1 = results[0], res2 = results[1], res3 = results[2] // 返回所有的结果,包裹在数组中
}).catch(err => console.error(err))
Promise.race([getJSON(url1),getJSON(url2),getJSON(url3)].then(res => {
console.log(res + 'is the first one finished'); // 只返回最快返回的结果
})).catch(err=>console.error(err))
能看到这里相信你就能够理解文章最开始写的例子了
最后:async
和 await
华丽登场,其实本质上就是 promise
和 generator
的结合,可以让异步的代码写得跟同步代码一样
async function(){
try {
const project = await getJSON('lorry.com');
const mission = await getJSON(project[0].missionURL);
const missionDetail = await getJSON(mission.detailURL);
}
}
async 告诉js引擎这个函数是异步的函数,可以不用等待结果继续执行.
await表示需要等待异步的返回结果之后才会继续执行,即此处是阻塞的.
相当于之前写的promise resolve 之后的执行过程
await 只是一个表达式,跟加减乘除没什么区别,哪怕不是异步代码,也可以使用await等,只是跟不加await一样而已.
function takeTime() {
return new Promise(resolve => setTimeout(resolve(2),1000))
}
async function test() {
/**相当于
let result
takeTime().then(res => res = result)
少了个外层变量的声明
*/
let result = await takeTime();
console.log(result)
}
// 返回的依然是一个Promise对象,状态为resolved
test();
// 也可以await同步代码
function sync() {
return 2
}
async function add() {
const a = await takeTime()
const b = a + await takeTime()
const c = a + await sync()
console.log(a) // 2
console.log(b) // 4
console.log(c) // 4
}
function err() {
return new Promise((resolve,reject)=>reject('我是故意的'))
}
async function testError () {
let error
await err().catch(err=> error=err) // 把err()是为一个promise即可
console.log(error)
}
在这里想强调一个 await 中一个比较容易跳进去的坑.
async function wait_for_possible() {
await Promise(r => setTimeout(r, 1000))
const coin = Math.random();
if (coin > 0.5) {
return 'yahhhh'
} else {
throw Error('bad luck!')
}
}
// case 1
async function foo_1() {
try {
wait_for_possible()
} catch(e) {
return 'I caught you!'
}
}
// case 2
async function foo_2() {
try {
await wait_for_possible()
} catch(e) {
return 'I caught you!'
}
}
// case 3
async function foo_3() {
try {
return wait_for_possible()
} catch(e) {
return 'I caught you!'
}
}
// case 4
async function foo_4() {
try {
return await wait_for_possible()
} catch(e) {
return 'I caught you!'
}
finally {
// 把所有需要 release 掉的东西放这里, 在 break 之后只有这个地方会执行.
}
}
在于 try-catch联合使用时一定要注意同异步的问题, 因为 catch 只能 catch 到同步的错误, 不管是直接执行( case 1) 还是返回( case 3), 都 catch 不到错误, 因为 promise 还在 pending, 并没有错误, 所以 foo 的代码已经执行完毕, 之后 throw 出来的错误就会到未捕获错误里.只有 case 2 和 case 4 是可以正常使用和捕获的.
async 会将整个函数的返回值📦成一个Promise, 不信你可以wait_for_possible() instanceof Promise
试试, 返回一定是 true.也就是说 async 本身就是promise.wrap 的一种实现方式
for-await, 可以在循环中使用 await, 保证每个 iterator 都决议完毕之后再进入循环.可以将生成器和 async-await 结合
async function* asyncRandomNumber() {
const url = 'https://www.random.org/decimal-fractions/?num=1&dec=10&col=1&format=plain&rnd=new';
while(true) {
const response = await fetch(url);
// 该 text 也为异步方法
const text = await response.text();
yield Number(text)
}
}
async function foo() {
for await (const number of asyncRandomNumber()) {
console.log(number)
if (number > 0.9) break;
}
}
foo()
使用 [Symbol.asyncIterator]
function streamAsyncIterator(stream) {
let i = 0;
return {
next() {
// Stream reads already resolve with {done, value}, so
// we can just call read:
return {done:false, value: i++}
},
return() {
// Release the lock if the iterator terminates.
return {done:true, value: 'iterator done'};
},
// for-await calls this on whatever it's passed, so
// iterators tend to return themselves.
[Symbol.asyncIterator]() {
return this;
}
};
}
async function foo() {
for await (const a of streamAsyncIterator()) {
if (a > 10) {
return a;
}
console.log(a)
}
}
foo().then(v => console.log(v))
上述代码打印的结果为 0,1,2,3,4,5,6,7,8,9,10, 11
既然Promise那么优秀, 可以实现异步中控制权的反转再反转, 还可信. 有没有一种办法可以直接将普通的回调函数转化为Promise呢?可以借助一些第三方库,也可以自己写一个简单的Promise.wrap函数, 假设回调都是error-first
的风格
if(!Promise.wrap) {
Promise.wrap = fn => (...args) => new Promise((resolve, reject) => {
fn.apply(null, args.concat((err, value) => {
err && reject(err)
resolve(value)
}))
})
}
这样就完成了一个简单的封装, 也是高阶函数的应用.
接下来看看如何使用吧
function fake(a, b, cb) {
setTimeout(()=> cb(a,b), 2000);
}
const wrapFunc = Promise.wrap(fake)
wrapFunc(null, 2).then((res) => console.log(res)); // 2s后打印2
generator中的辅助函数
step
function step(gen) {
// 闭包
const it = gen();
let last;
return (value) => {
// 不管yield出来的是什么都会传回去
last = it.next(value).value;
return last
}
}
// 使用方法
let i = 1;
function *gen1() {
while(true) {
yield (i ++)
}
}
function *gen2() {
let x = yield (i = i + 2);
yield x * 10
}
let newGen1 = step(gen1)
let newGen2 = step(gen2)
newGen1() // 1
newGen1() // 2
newGen1() // 3
newGen2() // 6
newGen2(10) // 100
newGen1() // 6
相信在开发中不会写这样的代码,只是为了更好的演示控制权在生成器中的交换以及信息的传递
ES6中有一个新的基本类型 Symbol
,
let something = (() => {
let nextVal = 0;
return {
[Symbol.iterator]: function() {return this},
next: () => {
if (nextVal === 0) {
nextVal = 1
} else {
nextVal = (3 * nextVal)
}
return {done: false, value: nextVal}
}
}
})();
// 还可以把iterator抽离出来
let nextObj = {
nextVal: 0,
next: function() {
if (this.nextVal === 0) {
this.nextVal += 1
} else {
this.nextVal *= 3
}
return {done: false, value: this.nextVal}
},
}
let something = (() => {
return {
[Symbol.iterator]: () => nextObj,
}
})()
something就是一个可迭代的对象, 并且永远可迭代.使用for...of
来看看
for (let ret of something) {
if (ret < 100) {
console.log(ret)
} else {
break;
}
}
每一个可迭代的对象都会有一个[Symbol.iterator]
, 用来每次迭代的时候返回的内容, 会调用next方法, 并将最后return的value值传回.而且会保存状态
let a = [1,2,3,4]
const it = a[Symbol.iterator]()
it.next().value; // 1
for (let i of it) {
console.log(i); // 2,3,4
}
[Symbol.iterator]
从同一个iterator对象出来的链式[Symbol.iterator]
每一个不同但是效果相同
let a = [1,2,3,4]
const it = a[Symbol.iterator]()
const it2 = it[Symbol.iterator]()
const it3 = it2[Symbol.iterator]()
const it4 = it[Symbol.iterator]()
Boolean(it == it2 || it2 == it3 || it3 == it4) // false
it.next().value;// 1
it2.next().value;// 2
it3.next().value;// 3
it4.next().value;// 4
注意上述的例子中使用了break来阻止死循环,其实generator自己有一个跳出循环的方法, it.return()
function *something() {
try {
let nextVal = 0;
while(true) {
if (nextVal === 0) {
nextVal += 1
} else {
nextVal *= 3
}
yield nextVal
}
}
finally {
console.log('clean up')
}
}
const it = something()
for (let ret of it) {
if (ret < 100) {
console.log(ret)
} else {
console.log(it.return('hello world').value)
}
}