深入理解javascript异步模式
发布于 7 年前 作者 huangyanxiong01 4328 次浏览 来自 分享

Callback 函数

当IO操作完成就回调函数,简单又熟悉


fs.readFile('/path/to/file', (err, data) => {
  if (err) {
     return console.error('读取文件错误')
  }

  console.log(data)
})

回调陷阱

  • 处理错误后忘记return

fs.readFile('/path/to/file', (err, data) => {
    if (err) { // 文件不存在
        /*return*/
        console.error('文件没有找到')
    }

    console.log('找到文件后做某些操作!')
})

  • 使用不同的参数多次调用回调函数
function doThingAsync(cb) {
    fs.readFile('/path/to/file', (err, data) => {
        if (error) {
            cb(err)
        }

        if (data.toString() === 'special') {
            cb(null, '数据已找到')
        }

        cb(null, '没有数据special')
    })
}

doThingAsync(function (err, data) {
    console.log('我会执行两次!')
})

解决回调陷阱

  • 在任何时候回调之后就return
if (err) {
    return callback(err)    
}

return callback(null, data)
  • 保证执行一次
doMyAsyncThing(_.once((err, data) => {
//    这个方法只可以执行一次,
})

_.once由lodash提供

回调提示

提高可读性,避免嵌套减少名称冲突

fetchUser(user_id, (err, user) => {
    fetchPosts(user_id, (err2, posts) => {
        fetchSomethingElse(user_id, (err3, other_stuff) => {
            if (err) { /* OOPS!应该使用 err3! */ }
        })
    })
})

正确姿势

function onPostsRetrieved(err, posts) { /* ... 获取数据 */ }

function onUserRetrieved(err, user) {
    fetchPosts(user.user_id, onPostsRetrieved)
}

fetchUser(user_id, onUserRetrieved)

Promises

所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果 promises

  • Promise.resolve

创建指定resolved状态值的Promise

const foo = Promise.resolve('last')


foo.then(x => {
    console.log('接着执行 ' + x)
})

foo.then(x => {
    console.log('再接下来执行 ' + x)
})

console.log('最先被执行')
  • Promise.reject

创建指定rejectd状态值的Promise

const foo = Promise.reject('rejection!')

foo.then(x => {
    console.log('这里不会被执行,因为Promise进入rejected状态')
})

foo.catch(x => {
    console.log(x)
})

foo.catch(x => {
    console.log('Another ' + x)
})
  • new Promise()

提供resolve和reject回调

new Promise((resolve, reject) => {
    fs.readFile('/path/to/file', (err, data) => {
        if (err) {
            return reject(err)
        }

        return resolve(data)
    })
})
.then(data => {
    console.log(data)
})
.catch(err => {
    console.log(err)
})
  • 组合使用Promises
const promise1 = Promise.resolve(4);
const promise2 = Promise.resolve(5);

promise1.then(a => {
    /*return promise2.then(b => {
        return b+a;
    })*/
    return {name:'test'};
}).then(c => {
    console.info(c); //{ name: 'test' }
});

从then返回的任何值都会被添加到promise链

  • Promise chain
promiseA
.then(() => throw new Error('Error')) // 假设 promiseB 抛出一个错误.
.catch(err => console.log(err)) // C1
.then(() => promiseC)
.then(() => promiseD)
.catch(err => console.log(err)) // C2

promise-flow

promiseA
.then(() => throw new Error('Error'))
.catch(err => throw err) // C1
.then(() => promiseC)
.then(() => promiseD)
.catch(err => console.log(err)) // C2

promise-flow1

  • Promise.all

Promise.all(promise集合) 方法返回一个promise,该promise会等promise集合内的所有promise都被resolve后被resolve,或以第一个promise被reject的原因而reject

const userService = require('./services/user')

const users_promise = Promise.all([ 
    userService.get(123),     
    userService.get(555)
])

users_promise.then((users) => {
    let [user123,user555] = users;
    console.log('全部用户服务已经执行成功!')
})
.catch((error) => {
    console.log('其中一个用户服务调用失败!')
})

对于执行顺序不重要的任务可以使用

  • Promise.race

Promise.race(promise集合)方法返回一个promise,这个promise在promise集合中的任意一个promise被解决或拒绝后,立刻以相同的解决值被解决或以相同的拒绝原因被拒绝。

const timeout = new Promise((resolve, reject) => {
    setTimeout(reject, 10000)
})

Promise.race([ timeout, userService.get(123) ])
.then(user => {
    console.log(user)
})
.catch(error => {
    // 错误有可能来自timeout或者userService.get
    console.log(error) 
})

Promise的陷阱

  • 忘记处理错误
Promise.reject('original')
.catch(err => {
    return Promise.resolve('default')
})
.then(res => {
    console.log(res) // res -> 来自catch的返回值
})
Promise.reject('original')
.then(res => {
    console.log(res) // 不会被执行
})
.catch(err => {
    return Promise.resolve('default')
})
  • 忘记返回Promise
Promise.resolve('/path/to/file')
.then(filename => {
    /*return*/ new Promise((resolve, reject) => {
        fs.readFile(filename, (err, data) => {
            if(err) { return reject(err) }
            return resolve(data)
        })
    })
})
.then(res => {
    console.log('result: ' + res)
})
  • 对event loop事件轮询的冲击
const userService = require('./services/user')

// 如果user基数非常大
const users_to_fetch = [1, 2, 5, 19, ... 9999] 

const get_users_promises = _.map(users_to_fetch, (user_id) => {
    return userService.get(user_id)
})

// 注意: 全部的Promise任务已经开始运行!
Promise.all(get_users_promises)
  • 解决event loop事件轮询的冲击

使用bluebird map方法

function timeoutPromise(timeout) {
    return new Promise(resolve => {
        setTimeout(resolve, timeout)    
    })
}

const things_to_do = new Array(100)

// Promise.map 来自 bluebird (没有可用原生接口实现) -- 大概在两秒内完成
Promise.map(things_to_do, () => timeoutPromise(200), { concurrency: 10 })
.then(() => console.log('Batch complete!'))

Promise.all(things_to_do.map(() => timeoutPromise(200)))
.then(() => console.log('Flood complete!'))

Generator

Generator函数是ES6提供的一种异步编程解决方案,语法行为与传统函数完全不同

  • 基本
function *oneGenerator() {
    const a = yield 1
    const b = yield 1

    console.log('result 1: ' + a)
    console.log('result 2: ' + b)
}

// 创建生成器实例
const generator = oneGenerator()

//第一次输入将会被忽略
console.log(generator.next('a').value)

console.log(generator.next('b').value)

console.log(generator.next('c').value)
  • Generator不会阻塞事件轮询

生成无限序列或推迟计算

function *myGenerator() {
    let i = 1
    while (true) {
        yield i++ 
    }
}

const generator = myGenerator()

console.log(generator.next().value)

console.log(generator.next().value)

console.log(generator.next().value)
  • 组合使用生成器
function *oneGenerator() {
    yield 1
    yield* twoGenerator()
    yield 1
}

function *twoGenerator() {
    yield 2
}

const generator = oneGenerator()

console.log(generator.next().value) // 运行generator的第一个yield.
console.log(generator.next().value) // 现在执行twoGenerator!
console.log(generator.next().value) // 返回到oneGenerator
  • Generator辅助异步的执行 通过组合Generator和Promise执行异步操作

Promise.coroutine(function* () {
    yield Promise.delay(500)

    console.log('after first delay')

    yield Promise.delay(1000)

    console.log('延迟几秒后')
})()

Promise.coroutine由bluebired提供,此外还有tj的CO库

  • (Generator + Promises) vs Promises
Promise.coroutine(function* () {
    try {
        const resultA = yield Promise.resolve('foobar')
        const resultB = yield Promise.resolve('baz')
        const resultC = yield Promise.resolve('super')
        console.log('GENERATORS: ' + resultA + resultB + resultC)
    } catch (error) { console.log(error) }
})()
Promise.resolve('foobar')
.then(resultA => {
    return Promise.resolve('baz')
    .then(resultB => {
        return Promise.resolve('super')
        .then(resultC => console.log('NESTED: ' + resultA + resultB + resultC))
    })
})
.catch(error => console.log(error))
  • Generator与Promise并行异步任务
Promise.coroutine(function *() {
    const results = yield Promise.all([
        Promise.resolve(1),
        Promise.resolve(2)
    ])

    console.log('array', results[0], results[1])

    const [a, b] = yield Promise.all([
        Promise.resolve(1),
        Promise.resolve(2)
    ])

    console.log('destructured', a, b)
})()

生成器默认支持Node.js版本4+

async/await

  • 从Generator到async/await
Promise.coroutine(function* () {
    const [a, b] = yield Promise.all([
        Promise.resolve(1),
        Promise.resolve(2)
    ])

    console.log(a, b)
})()
async function () {
    const [a, b] = await Promise.all([
        Promise.resolve(1),
        Promise.resolve(2)
    ])

    console.log(a, b)
}
  • 调试异步JavaScript

为作为参数的函数命名

function doMyThing() {
    setTimeout(() => { 
        console.log('NOT NAMED: ' + new Error().stack)
    }, 50)
}
doMyThing()

正确的方式

function doMyThing() {
    setTimeout(function fooIsMyName() { 
        console.log('    NAMED: ' + new Error().stack)
    }, 50)
}

doMyThing()
  • chrome中开启async追踪

async

Thank

Joseph Andaverde

MyFreax整理

2 回复

@i5ting 哈哈,还是狼叔的详尽

回到顶部