JavaScript Promise链式调用(二)

1,194 阅读4分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

今天我们来复习promise的链式调用的使用方法,使用链式调用可以解决顺序执行异步操作。

promise链式调用

在JS中我们常用的链式调用应该是jQuery框架了,比如下面的代码

$('.level1').click(function () {
  $(this)
    .next()
    .stop()
    .slideToggle()
    .parent()
    .sibling()
    .children('ul')
    .slideUp();
});

Promise对象的实例方法then()、catch()、finally()其实会返回一个promise对象。

所以,可以在返回的promise对象上继续调用then()等实例方法,这种连续调用的方式称为promise链。

下面举个简单例子

创建一个promise对象,3秒后成功返回一个数字10。

let p = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(10);
  }, 3 * 1000);
});

p.then((result) => {
  console.log(result);
  return result * 2;
});
// 3秒后输出:
// 10

在then()回调函数内我返回一个新值result * 2,因为then会返回一个新的promise对象,返回的值相当于resolve的值。所以可以接着继续调用then方法,像下面这样

let p = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(10);
  }, 3 * 1000);
});

p.then((result) => {
  console.log(result);
  return result * 2;
}).then((result) => {
  console.log(result);
  return result * 3;
});

// 3秒后输出:
// 10
// 20

还可以接着继续then下去

let p = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(10);
  }, 3 * 1000);
});

p.then((result) => {
  console.log(result);
  return result * 2;
}).then((result) => {
  console.log(result);
  return result * 3;
}).then((result) => {
  console.log(result);
  return result * 4;
});

// 3秒后输出:
// 10
// 20
// 60

JavaScript-Promise-Chaining

多个then

不使用链式调用,同时在一个promise上调用多个then方法

let p = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(10);
  }, 3 * 1000);
});

p.then((result) => {
  console.log(result); // 10
  return result * 2;
})

p.then((result) => {
  console.log(result); // 10
  return result * 3;
})

p.then((result) => {
  console.log(result); // 10
  return result * 4;
});

// 3秒后输出:
// 10
// 10
// 10

在上面例子中,一个promise对象,多个then,这些then之间没有任何关联。它们独立执行,不会像上面链式调用那样。

JavaScript-Promise-Chaining-multiple-handlers

在实际项目中,很少会这样给一个promise调用多个then方法。

return new promise

上面我们在then的回调函数里去return一个值,其实就相当于去new一个promise然后立刻reslove该值。

像下面这样

let p = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(10);
  }, 3 * 1000);
});

p.then((result) => {
  console.log(result);
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(result * 2);
    }, 3 * 1000);
  });
}).then((result) => {
  console.log(result);
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(result * 3);
    }, 3 * 1000);
  });
}).then(result => console.log(result));

// 每隔3秒后输出:
// 10
// 20
// 60

接着,我们可以封装一个函数,用来返回promise

// 生成promise
function generateNumber(num) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(num);
    }, 3 * 1000);
  });
}

generateNumber(10)
  .then(result => {
    console.log(result);
    return generateNumber(result * 2);
  })
  .then((result) => {
    console.log(result);
    return generateNumber(result * 3);
  })
  .then(result => console.log(result));

// 每隔3秒后输出:
// 10
// 20
// 60

和上面的结果一样,我们用generateNumber函数做了一个封装。

promise链式语法

在JS开发中,我们经常遇到需要按顺序去执行多个异步任务。下一个异步任务依赖上一个异步任务的结果。如果不用没有promise,使用callback将会是这样

Get('/user', function(user) {
  Get(`/level/${user.id}`, function(level) {
    Get(`/info/${level.id}`, function(info) {
      console.log(info)
    })
  })
})

使用promise链式调用

getUser()
  .then(result => getLevel(result))
  .then(result => getInfo(result))
  ...

相比callback方式来说,看着会更加清晰,callback方式会无限的嵌套下去,promise只需依次向下继续then就可以了。

下面举个实际的例子,

  • 从数据库中获取数据
  • 获取该用户购物车中的商品
  • 计算商品的总价

假设上面的操作都为异步操作,并且需要顺序执行

function getUser(userId) {
  return new Promise((resolve, reject) => {
    console.log('从数据库中获取用户信息');
    setTimeout(() => {
      resolve({
        userId: userId,
        username: 'admin'
      });
    }, 1000);
  })
}

function getServiceCart(user) {
  return new Promise((resolve, reject) => {
    console.log(`获取 ${user.username} 的购物车商品`);
    setTimeout(() => {
      resolve([
        {name: 'book', price: 10},
        {name: 'phone', price: 200} 
      ]);
    }, 3 * 1000);
  });
}

function getServiceCost(goods) {
  return new Promise((resolve, reject) => {
    console.log(`计算购物车商品的总价: ${JSON.stringify(goods)}.`);
    setTimeout(() => {
      resolve(goods.reduce((total, cur) => total + cur.price, 0));
    }, 2 * 1000);
  });
}

getUser(100)
  .then(getServiceCart)
  .then(getServiceCost)
  .then(console.log);

// 输出:
// 从数据库中获取用户信息
// 获取 admin 的购物车商品
// 计算购车商品的总价: [{"name":"book","price":10},{"name":"phone","price":200}].
// 210

promise解决了回调地狱的问题,但仍不够优雅。

在ES2017中引入了async/await关键字,可以写出比promise更加简洁的代码,后面再写。

今天我们学习了promise用来处理多个异步任务的promise链式用法。

欢迎关注我公众号【小帅的编程笔记】,跟着我每天进步一点点