let Promise = require('./3.1.promise.js')
let p = new Promise((resolve,reject) => {
resolve('xx')
})
p.then((data) => {
console.log('p success',data)
}, (err) => {
console.log(err)
})
// => p success xx
let Promise = require('./3.1.promise.js')
let p2 = new Promise((resolve,reject) => {
setTimeout(() => {
resolve('xxx')
}, 1000)
})
p2.then((data) => {
console.log('p2 success',data)
}, (err) => {
console.log(err)
})
// => p success xx
// p second success xx
let it = say ()
let flag = false
do{
let {value, done} = it.next()
console.log(value)
flag = done
}while(!flag)
// => node
// react
// vue
// undefined
function * say() {
let a = yield 'hello'
console.log('a', a)
let b = yield 'careteen'
console.log('b', b)
let c = yield 'lanlan'
console.log(c)
}
let it = say()
it.next(100) // 第一次next传递参数 是无意义的
it.next(200)
it.next(300)
// => a 200
// b 300
异步发展流程-手摸手带你实现一个promise
篇幅较长,但重点为以下几点,可直接前往感兴趣的话题,各取所需。
所有涉及的例子均有完整代码存放在仓库,感兴趣的同学可直接clone在本地运行。
本文主要简单探讨下异步的前世今生,并手摸手带你实现一个promise
由于JavaScript单线程的特性,我们需要异步编程解决阻塞问题。
异步编程问题
我们每天的工作中都可能会用到以下函数做一些异步操作
如何解决异步问题
解决异步问题现有的方式如下
下面将逐一介绍各种方式如何解决异步问题
回调函数
首先介绍一下高阶函数,即一个函数的参数是函数或者函数返回值为函数,此函数称做高阶函数。
lodash-after函数
再来看一个例子,常使用lodash的同学应该熟悉的一个方法_.after(n, fn),作用是fn函数在调用n次以后才会执行。
那如何实现一个
after
函数呢,其实主要是利用闭包和计数的思想:其中
cb
作为函数参数传入after
函数,即是高阶函数的一个应用。after函数例子地址
⬆️回到顶部
Node读取文件
现在有一个场景,读取两个文件内容,赋值给一个对象,并打印。
在./static下新建了两个文件
name.txt
,age.txt
,期望读取文件内容并赋值给一个对象,然后打印。由于读取文件的过程是异步的,所以通过这种方式是无法满足预期的。
并且异步操作存在以下三个问题
回调地狱大家应该非常熟悉了。
本例地址
并且两个文件读取时间是累加,不是并行的,如果文件很多并且很大,那等待时间将非常久,所以并不推荐。
这里针对第三个问题多个异步操作,在同一时间内,如何同步异步的结果?,可以采用发布订阅的方式解决
不了解发布订阅模式的请移步我的另一篇博客
通过以下操作即可达到预期
在每次读取文件时触发打印事件,事件中进行判断只有两次读取都完成的情况下才会打印。
以上方法看似解决了上面提到的第三个问题多个异步操作,在同一时间内,同步异步的结果,但是随着需求的变动,需要再读取一个
address
文件,就需作如下变动:再新增多项的话,代码的扩展性就非常差了。
下面将将介绍如何实现一个promise然后解决上面提到的问题
node读取文件代码地址
⬆️回到顶部
为什么要用promise
那么接下来介绍promise的出现所解决的问题
promise用法
手摸手带你撸一个promise
首先需要提到promise/A+规范,我们自己编写的promise是需要一个标准的。可以根据此标准一步一步来。
需要三个状态
pending
时fulfilled
或rejected
fulfilled
或rejected
时value
或reason
且不能改变⬆️回到顶部
then方法
更详细请移步文档,这里说几个重点
executor
函数中代码异常的情况executor
函数中代码为异步的情况处理
executor
函数中代码异常的情况对
executor
try-catch即可如下使用
简易版1.0.0地址以及测试用例地址
虽然实现了一个很简易的promise,但还存在很多问题,比如下面
对于异步的代码是不会处理的
⬆️回到顶部
处理
executor
函数中代码为异步的情况使用发布订阅模式的思想处理
使用
简易版1.0.1地址以及测试用例地址
⬆️回到顶部
处理then的链式调用
和
jQuery
的链式调用一个套路,不过在这儿需要返回一个新的promise
而不是当前,因为成功态和失败态是不能转为其他状态的使用
简易版1.0.2地址以及测试用例地址
如代码中只是简单处理
_resolvePromise
方法⬆️回到顶部
完善_resolvePromise
再移步规范文档处理_resolvePromise
需要考虑以下几种情况
_resolvePromise (promise2, x, resolve, reject)
考虑以上进行完善
使用
简易版1.0.3地址以及测试用例地址
⬆️回到顶部
以上一个符合
Promise/A+
规范的promise基本完成那怎么验证自己写的promise是否正确呢?
追加以下
deferred
方法以供检查安装检查工具
promises-aplus-tests
执行检查
都是绿色表示检查通过
代码地址
⬆️回到顶部
promise周边
以上只是一个简易的promise,我们期望完善更多功能:
下面实现的地址在简易版1.1.1以及测试用例
catch方法
实现
使用
⬆️回到顶部
静态方法
实现
使用
⬆️回到顶部
finally方法
实现
使用
⬆️回到顶部
all方法
实现
使用
⬆️回到顶部
race方法
实现
使用
⬆️回到顶部
generator用法
在理解generator之前先看一个例子。
实现一个可传任意个参数的加法函数
老司机们三下五除二就能得出求和函数
以上代码地址
使用ES6的展开运算符
...
可枚举出所有参数,再用数组包裹,即可将一个类数组转换为一个数组。利用reduce实现累加,方可得出求和函数。那展开运算符能否操作对象佯装的类数组呢?那就来试一试
以上代码地址
可得知对象是不能被迭代的,根据报错信息,我们再改进代码
再使用generator实现
以上代码地址
生成器可以实现生成迭代器,生成器函数就是在函数关键字中加个*再配合yield来使用,并且yield是有暂停功能的。
那如何遍历迭代器呢?
以上代码地址
迭代器提供
next
方法,可得出迭代的value
和是否已经迭代完成done
,用一个循环即可遍历。yield的返回值的使用场景
以上代码地址
generator执行流程大体如下图
可看出第一次next传递参数是无意义的,所以输出结果为
a 200 b 300
以上均为同步的情况,接下来看下yield后面是异步的场景。
再通过一个例子来深入理解。
通过读取文件
1.txt
的内容为2.txt
,再读取2.txt
的内容为3.txt
,最后读取3.txt
中的内容Careteen
首先需要准备三个文件,放置在
./static
目录下,再准备读取文件的函数,在使用generator实现读取函数
期望的过程是通过读取文件
1.txt
的内容为2.txt
,再读取2.txt
的内容为3.txt
,最后读取3.txt
中的内容Careteen
,进行返回。首先我们能想到使用回调的方式解决,因为yield后面是一个promise
但这又会产生回调地狱,所以需要优化,我们需要一个迭代函数,通过递归可以实现
使得异步可以按顺序来执行,最后看一下执行
以上代码地址
非常完美的实现了,但是如果yield的后面是一个同步操作,没有then方法,在
co
方法中我们还需要特殊处理,也比较简单。牛逼的TJ大神的CO库就对此做了很完善的处理,感兴趣的可前往仓库看看源码,只有200多行。
generator的应用:
如何实现generator
可查看babel编译后的结果
⬆️回到顶部
async-await
写起来是同步的,语法糖很甜不腻。
bluebird
async-await
async-await内部机制
例子
通过babel编译后可看出实质上是通过
generator+co
的方式实现的。⬆️回到顶部