深入了解 Promise

Promise 进阶核心

下面部分摘录We have a problem with promises

每一个 promise 都会提供给你一个 then() 函数 (或是 catch(),实际上只是 then(null, ...) 的语法糖)。当我们在 then() 函数内部时

somePromise().then(function () {  
  // I'm inside a then() function!
});

我们可以做什么呢?有三种事情:

  • return 另一个 promise
  • return 一个同步的值 (或者 undefined)
  • throw 一个同步异常

就是这样。一旦你理解了这个技巧,你就理解了 promises。因此让我们逐个了解下。

返回另一个 promise

这是一个在 promise 文档中常见的使用模式,也就是我们在上文中提到的 “composing promises”:

getUserByName('nolan').then(function (user) {  
  return getUserAccountById(user.id);
}).then(function (userAccount) {
  // I got a user account!
});

注意到我是 return 第二个 promise,这个 return 非常重要。如果我没有写 returngetUserAccountById() 就会成为一个副作用,并且下一个函数将会接收到 undefined 而非 userAccount

返回一个同步值 (或者 undefined)

返回 undefined 通常是错误的,但是返回一个同步值实际上是将同步代码包裹为 promise 风格代码的一种非常赞的手段。举例来说,我们对 users 信息有一个内存缓存。我们可以这样做:

getUserByName('nolan').then(function (user) {  
  if (inMemoryCache[user.id]) {
    return inMemoryCache[user.id];    // returning a synchronous value!
  }
  return getUserAccountById(user.id); // returning a promise!
}).then(function (userAccount) {
  // I got a user account!
});

是不是很赞?第二个函数不需要关心 userAccount 是从同步方法还是异步方法中获取的,并且第一个函数可以非常自由的返回一个同步或者异步值。

不幸的是,有一个不便的现实是在 JavaScript 中无返回值函数在技术上是返回 undefined,这就意味着当你本意是返回某些值时,你很容易会不经意间引入副作用。

出于这个原因,我个人养成了在 then() 函数内部 永远返回或抛出 的习惯。我建议你也这样做。

实战

将带回调的函数,包装成 Promise

module.exports = fn => {  
  return (...args) => {
    return new Promise((resolve, reject) => {
      args.push((err, result, ...other) => {
        err ? reject(err) : resolve(result, other)
      })
      fn(...args)
    })
  }
}

利用 ES6的新特性,我们仅用10行代码就写出了一个 promisify的函数

Promise 的并行

我们先写一个用来测试使用的异步函数delay,用来延迟100ms 后返回传进来的数字

const delay = number => {  
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(number)
    }, 100)
  })
}

promise 的并行非常的简单,我们可以使用 Promise.all来对一个 Promise 数组进行并行。 下面的代码是对一个数组进行并行执行 delay

// 使用 ava 进行测试
import test from 'ava';

const numbers = [1, 2, 3, 4, 5];

test('parallel', async t => {  
  console.time('parallel');
  t.deepEqual(await Promise.all(numbers.map(delay)), numbers);
  console.timeEnd('parallel');
});

Promise 的串行

Promise 的串行实现起来比较麻烦(而且我也没想到比较好的应用场景),不过实现 Promise 的串行可以让我们非常好的理解 Promise 本身

Promise 实现的原理是我们要实现一个 Promise 顺序执行串

类似这样

promise1.then(data1 => {return promise2(data1)}).then(data2 => {return promise3(data2)})...  

真正的实现如下

// 使用 ava 进行测试
import test from 'ava';

const numbers = [1, 2, 3, 4, 5];

const series = factories => {  
  // 看懂这行你就明白 Promise 了吧
  return factories.reduce((resolve, factory) => resolve.then(number => factory()), Promise.resolve())
}

const factory = number => () => delay(number)

test('serial', async t => {  
  console.time('serial');
  t.is(await series(numbers.map(factory)), numbers[numbers.length -1])
  console.timeEnd('serial');
})

其他