Promise/Async/Generator
Simple callback
difficult error handling because try...catch don’t work
getJSON('xx.json', (err, data) => {
if (err) console.log('err')
// do something
})
sequences of steps, leading to pyramid of doom
getJSON('xx.json', (err, data) => {
if (err) console.log('err')
invite(data[0].name, (err, data) => {
if (err) console.log('err')
// do something
})
})
waiting until all the parallel tasks are done
let person, item,
getPerson('xx', (err, data) => {
if (err) console.log('err')
person = data
checkReady()
})
getItem('xx', (err, data) => {
if (err) console.log('err')
item = data
checkReady()
})
function checkReady() {
if (!person && !item) {
console.log('ready go')
}
}
Promise
executor
console.log('1')
let promise = new Promise(function(resolve, reject) {
console.log('2')
setTimeout(() => {
resolve("done")
resolve('ok') // ignored
reject("my error") // ignored
}, 1000)
})
promise.then((val) => {
console.log('val: ', val)
})
// 1, 2, val: done
executor()会同步执行executor()接收2个参数:resolve, reject- 任务成功调用
resolve(val) - 失败则调用
reject(err)
- 任务成功调用
- 一旦调用
resolve或者reject,再次调用都会被忽略,换句话说,此时promise is settled,状态不会再发生变化
简单实现如下
const PEDNING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class MyPromise {
constructor(executor) {
this.state = PEDNING
this._resolvedQueue = []
this._rejectedQueue = []
const _resolve = (v) => {
if (this.state !== PEDNING) return
this.state = FULFILLED
this._resolvedQueue.forEach((f) => f(v))
}
const _reject = (err) => {
if (this.state !== PEDNING) return
this.state = REJECTED
this._rejectedQueue.forEach((f) => f(err))
}
executor(_resolve, _reject)
}
then(onFulfilled, onRejected) {
this._resolvedQueue.push(onFulfilled)
this._rejectedQueue.push(onRejected)
}
}
但是,executor 不一定是异步操作,比如任务已经完成,直接从缓存获取
let promise = new Promise(function(resolve, reject) {
resolve("done")
})
目前 MyPromise 无法支持,还需要进一步改进,一种方式是让 resolve 内部走一个异步,比如 setTimout
const _resolve = (v) => {
const step = () => {
if (this.state !== PEDNING) return
this.state = FULFILLED
this._resolvedQueue.forEach((f) => f(v))
}
setTimeout(step)
}
chaining
目前 MyPromise.then() 还过于简陋,标准的 then 链式调用还未实现
- then 返回新的 promiseA
- onFulfilled 返回值
- 返回另一个 promiseB,那么 promiseA 以 promiseB 的状态为准
- 返回非 promise 值,会带入下一个
then(onFulfilled)中
then(onFulfilled, onRejected) {
return new Promise((resolve, reject) => {
const _fulfilled = (val) => {
const ret = onFulfilled(val)
ret instanceof MyPromise ? ret.then(resolve, reject) : resolve(ret)
}
this._resolvedQueue.push(_fulfilled)
})
}
测试一下
new MyPromise(function(resolve, reject) {
setTimeout(() => { resolve("done") }, 1000)
})
.then((val) => {
console.log('val: ', val) // val: done
return new MyPromise((resolve) => resolve(val + 'seconds'))
})
.then((val) => console.log('second val: ', val)) // doneseconds
有时候,也不需要返回真正的 promise 对象,比如一个拥有 then 的对象
then(onFulfilled, onRejected) {
return new Promise((resolve, reject) => {
const _fulfilled = (val) => {
// ret instanceof MyPromise ? ret.then(resolve, reject) : resolve(ret)
// duck type
ret.then ? ret.then(resolve, reject) : resolve(ret)
}
this._rejecteddQueue.push(onRejected)
})
}
// 测试一下
class Thenable {
constructor(name) {
this.name = name;
}
then(resolve, reject) {
setTimeout(() => resolve('thenable: ' + this.name), 1000)
}
}
promise
.then((val) => {
console.log('val: ', val)
// return new MyPromise((resolve) => resolve(val + 'seconds'))
return new Thenable(val)
})
.then((val) => {
console.log('second val: ', val) // second val: thenable: done
})
error handling
// code 1
promise.then(fn, error).then(() => console.log('fulfilled'))
// code 2 in a promise chain, the last catch will catch all error
promise.then(fn).then(fn).catch(error)
// code 3 passing errors
promise().catch((err) => { throw err })
// code 4 implicit try...catch in executor
new Promise((resolve) => {
throw new Error('err')
}).catch(console.log)
// code 5
new Promise(function(resolve, reject) {
setTimeout(() => {
throw new Error("Whoops!")
}, 1000);
}).catch(console.log) // not catched
// code 6
window.addEventListener('unhandledrejection', function(event) {})
new Promise(function() {
throw new Error("Whoops!")
})
then(onFulfilled, onRejected) {
return new Promise((resolve, reject) => {
const _rejected = (val) => {
try {
if (!onRejected) {
reject()
} else {
const ret = onRejected(val)
// 注意这里的 resolve()
ret && ret.then ? ret.then(resolve, reject) : resolve(ret)
}
} catch (e) {
reject(e)
}
}
this._rejectedQueue.push(_rejected)
})
}
catch(cb) {
return this.then(undefined, cb)
}
finally
finally()没有入参,会自动把上个 promise 传递下去- 忽略 handler 内部的返回值
- handler 内部抛出错误,被最近的 error handler 捕获
// pass result
new Promise((resolve, reject) => {
setTimeout(() => resolve('done'), 2000);
})
.finally(() => Promise.resolve('ds'))
.then(result => console.log(result)) // done
// pass error
new Promise((resolve, reject) => {
throw new Error("error")
})
.finally(() => console.log("finally"))
.catch(err => console.log(err))
// throws error
new Promise((resolve, reject) => {
throw new Error("error")
})
.finally(() => { throw new Error("ds") })
.catch(err => console.log(err)) // ds
实现也比较简单
finally(cb) {
return this.then(
(val) => {
return MyPromise.resolve(cb()).then(() => val)
},
(e) => {
return MyPromise.resolve(cb()).then(() => { throw e })
}
)
}
API
class MyPromise {
static resolve(val) {
if (v instanceof MyPromise) return v
return new MyPromise((resolve) => resolve(v))
}
static reject(err) {
return new MyPromise((_, rej) => rej(err))
}
static all(asyncList) {
let result = []
let counter = asyncList.length
return new MyPromise((resolve, reject) => {
asyncList.forEach((p, i) => {
p.then(
(v) => {
result[i] = v
counter--
if (counter === 0) {{
resolve(result)
}}
},
(e) => {
reject(e)
}
)
})
})
}
static race() {
return new MyPromise((resolve, reject) => {
asyncList.forEach((p, i) => {
p.then(
(v) => {
resolve(v)
},
(e) => {
reject(e)
}
)
})
})
}
static allSettled() {}
static any() {}
}
Iterator
- iterable 决定是否可以 iteration
- iterator 定义 how iteration
iterator
The iterator protocol defines a standard way to produce a sequence of values (either finite or infinite), and potentially a return value when all values have been generated.
iterator 是一个通过 next() 实现 Iterator protocol 的对象,next() 会返回一个具有 { value: any: done: boolean} 的对象。iterator 需要手动调用 next() 会进行遍历,直到返回 { done: true } 结束
function rangeIterator(min, max, step = 1) {
let start = min
return {
next() {
if (start < max) {
const ret = { value: start, done: false }
start += step
return ret
}
return { value: max, done: true }
}
}
}
iterable
The iterable protocol allows JavaScript objects to define or customize their iteration behavior, such as what values are looped over in a for…of construct.
一个实现了 @@iterator 方法的对象,即这个对象拥有 Symbol.iterator 属性,返回遵循 iterator protocol 的对象
- 只能遍历一次(比如
generator),每次调用@@iterator都返回this - 也可以被遍历多次,每次调用
@@iterator都会返回一个新的iterator - 内置的
iterable object:String, Array, Map, Set... - 内置的
iterable API:Promise.all(), Array.from(), Map(), Set()... @@iterator可以是普通函数、generator
// case1 generator itselft is iterable, and it can iterate only once
function* step() {
yield 1;
yield 2;
}
const iterA = step()
console.log('a:', iterA.next()) // { value: 1, done: false }
const iterB = step()
console.log('b:', iterB.next()) // { value: 1, done: false }
console.log('b:', iterB.next()) // { value: 2, done: false }
console.log('a:', iterA.next()) // { value: 2, done: false }
// case2 retuns a new generator, which can iterate many time
const obj = {
*[Symbol.iterator]() {
yield 1
yield 2
yield 3
}
}
const iter1 = obj[Symbol.iterator]()
console.log('iter1: ', iter1.next()) // { value: 1, done: false }
console.log('iter1: ', iter1.next()) // { value: 2, done: false }
const iter2 = obj[Symbol.iterator]()
console.log('iter2: ', iter2.next()) // { value: 1, done: false }
// case3 普通函数 retuns a new iterator, which can iterate many time
const obj = {
[Symbol.iterator]() {
let start = 1
return {
next() {
if (start < 3) {
return { value: start++, done: false }
}
return { value: undefined, done: true }
}
}
}
}
哪些情况是以 iterables 作为参数?
for (const value of ["a", "b", "c"]) {
console.log(value);
}
[..."abc"]
[a, b, c] = new Set(["a", "b", "c"])
Generator
Regular functions return only one, single value (or nothing).
Generators can return (“yield”) multiple values, one after another, on-demand. They work great with iterables, allowing to create data streams with ease.
yield
- generator is a special type of iterator,and it’s iterable.
yieldreturns the result to the outside and passes the value to the generatornext()supplies the value to the waiitngyield, so if there is noyieldwaiting, this is no need to supply the value to. In other words, the first callnext()has no argument.
function* sum(a) {
const b = yield 2 + a
const c = yield b + 5
return c
}
const iterSum = sum(1) // pass parameter like other normal functions do
console.log(iterSum.next()) // 3, the first call `next()` has no argument.
console.log(iterSum.next(4)) // 9
console.log(iterSum.next(12)) // 12
throw
function* sum(a) {
try {
const b = yield 2 + a
const c = yield b + 5
return c
} catch (e) {
console.log('error:', e.message) // error: custom error
}
}
const iterSum = sum(1)
console.log(iterSum.next()) // 3
iterSum.throw(new Error('custom error'))
如果不在 generator 内捕获的话,error 会抛在调用 generator 的地方
function* sum(a) {
const b = yield 2 + a
const c = yield b + 5
return c
}
const iterSum = sum(1)
console.log(iterSum.next()) // 3
try {
iterSum.throw(new Error('custom error'))
} catch (e) {
console.log('error:', e.message) // error: custom error
}
under the hood
state machine

Source From: Secrets of the JavaScript Ninja-2nd
suspended start: 调用generator()进入起始状态executing: 调用next()进入此状态,即初次运行或者从上次suspended的地方继续suspended yield: 遇到yieldcompleted: 遇到return或者函数结束
execution context stack
以这段代码为例(以下代码和图来源于 《Secrets of the JavaScript Ninja-2nd》)
function* NinjaGenerator(action) {
yield "Hattori " + action
return "Yoshi " + action
}
const ninjaIterator = NinjaGenerator("skulk")
const result1 = ninjaIterator.next()
const result2 = ninjaIterator.next()
Step 1: NinjaGenerator("skulk")

Step 2: const result1 = ninjaIterator.next()

Step 3: const result2 = ninjaIterator.next()
同 Step 2
Async/Await
利用 generator 简单实现 async/await
function run(gen) {
const iter = gen()
const step = (val) => {
const res = iter.next(val)
if (res.done) return
res.value.then((v) => { step(v) }).catch(err => iter.throw(err))
}
step()
}
run(function *() {
try {
const a = yield Promise.resolve(1)
console.log('a', a)
const b = yield Promise.resolve(3)
console.log('b', b)
} catch (e) {
console.log('opps')
}
})