Skip to content
LYF edited this page May 25, 2017 · 24 revisions

一、 场景描述

有一个url,存储在一个名为url.file的文件内,内有一行json:

http://xxx.yyy.com/test.json

这个json的内容为:

{
 "name": "李彦峰",
 "age": 26
}

我想要先读取这个文件,然后下载这个url,并parse成一个对象,然后给age属性进行加1操作,然后在写回本地的json.file文件里。

整个流程为:读 -> 下载 -> 解析 -> age加1 -> 写回

二、 上述流程的编程实现

1. 最原始的回调方式

/**
 点评:这种方式很啰嗦,代码量多,回调嵌套回调,可读性也不好。
 这就是所谓的“回调地域”,好在我们parse的时候,是同步的。
 不然还要在parse的回调中调用write
**/

const read = (file, cb) => {
  fs.readFile(file, 'utf8', (err, url) => {
   if(err){
     console.error(err)
   } else {
     console.log('45:', url)
     cb(url)
   }
  })
}

const download = (url, cb) => {
  request(url, (err, response, json) => {
     if (!err && response.statusCode === 200) {
        cb(json)
     } else {
        console.error(err)
      }
    })
}

const parse = (res) => {
 const obj = JSON.parse(res)
 obj.age++
 return JSON.stringify(obj)
}

const write = (text, file = './json.file') => {
  fs.writeFile(file, text, 'utf8', (err) => {
    console.error(err)
  })
}

read('./url.file', (url) => {
   download(url, (res) => {
    const str = parse(res)
    write(str)
  })
})

2. Promise方式

Promise我们使用bluebird,为了减少代码我没有使用 return new Promise 的方式,而是使用了 Promise.promisify的方式,来promise化readFile/request/writeFile这三个异步方法

/**
 点评:这种方式相对于刀耕火种的回调函数方式就好了很多.
 代码很易读,就跟写文章一样,then这个单词就是*然后*的意思,
 我们可以看到代码的意思是:先读取`url.file`这个文件,然后下载,然后解析,然后写入。
 流程非常清晰,非常有语义化。
 最关键的是,异常处理也变得很方便。
**/

const read = (file) => Promise.promisify(fs.readFile)(file, 'utf8')
const download = (url) =>  Promise.promisify(request)(url)

const parse = (res) => {
 const obj = JSON.parse(res.body)
 obj.age++
 return JSON.stringify(obj)
}

const write = (text, file = './json.file') => Promise.promisify(fs.writeFile)(file, text, 'utf8')

read('./url.file')
 .then(download)
 .then(parse)
 .then(write)
 .catch(e => console.error(e))

3. async的方式

/**
 点评:这种方式个人感觉相对于Promise的方式又好了一些.
 把Promise的水平结构,拉成了垂直结构,跟传统的同步语言,比如Java、C等很像
**/
const read = (file) => Promise.promisify(fs.readFile)(file, 'utf8')
const download = (url) =>  Promise.promisify(request)(url)

const parse = (res) => {
 const obj = JSON.parse(res.body)
 obj.age++
 return JSON.stringify(obj)
}

const write = (text, file = './json.file') => Promise.promisify(fs.writeFile)(file, text, 'utf8')

const flow = async (file) => {
  const url = await read(file)
  const res = await download(url)
  const str = parse(res)
  write(str)
}

flow('./url.file')

4. async/await

  • async/await是写异步代码的新方式,以前的方法有回调函数和Promise。
  • async/await是基于Promise实现的,它不能用于普通的回调函数。
  • async/await与Promise一样,是非阻塞的。
  • async/await使得异步代码看起来像同步代码,这正是它的魔力所在。
  • async函数会隐式地返回一个promise,该promise的reosolve值就是函数return的值。

参考:http://www.cnblogs.com/fundebug/p/6667725.html

5. async/await效率提升

多个await命令后面的异步操作,如果不存在继发关系,最好让他们同时触发

let foo = await getFoo()
let bar = await getBar()

上面代码中,getFoogetBar是两个独立的异步操作(即不互相依赖),被写成继发关系。这样比较耗时,因为只有在getFoo完成以后,才会执行getBar,完全可以让他们同时触发。

// 写法一
let [foo, bar] = await Promise.all([getFoo(), getBar()])

// 写法二
let fooPromise = getFoo()
let barPromise = getBar()
let foo = await fooPromise
let bar = await barPromise

---------测试

function getFoo () {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('getFoo 执行了 ....')
    }, 2000)
  })
}

function getBar () {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('getBar 执行了 ....')
    }, 4000)
  })
}

async function call () {
  console.time('call\'s await')
  const foo = await getFoo()
  const bar = await getBar()
  console.timeEnd('call\'s await')
  console.log(foo)
  console.log(bar)
}

async function call2 () {
  console.time('call2\'s await')
  const [foo, bar] = await Promise.all([getFoo(), getBar()])
  console.timeEnd('call2\'s await')
  console.log(foo)
  console.log(bar)
}

async function call3 () {
  console.time('call3\'s await')
  const fooPromise = getFoo()
  const barPromise = getBar()
  const foo = await fooPromise
  const bar = await barPromise
  console.timeEnd('call3\'s await')
  console.log(foo)
  console.log(bar)
}

call()
call2()
call3()

测试环境:

Chrome 58
Node 7.10.0

打印信息:

call2's await: 4006.978ms
getFoo 执行了 ....
getBar 执行了 ....
call3's await: 4008.081ms
getFoo 执行了 ....
getBar 执行了 ....
call's await: 6008.222ms
getFoo 执行了 ....
getBar 执行了 ....

得出结论:

  1. 多个异步串行的话,则总时间为各个异步过程的用时的和
  2. 多个异步并行的话,则总时间为这些异步过程用时最长的那一个的时间
  3. 所以,如果没有下一步的异步要依赖上异步的结果这种情况,所有的await应该改为并行执行
  4. 出现上述的原因在于,浏览器和nodejs允许并行发送request http://blog.csdn.net/stevendbaguo/article/details/49620011
Clone this wiki locally