JS之Promise学习【中篇】

学习目标:

可以看懂原有的用promise实现的代码,也可以做到根据日常需求判断什么场景下使用promise,以及promise怎么去用。
了解promise链式调用,并可以应用到日常需求中。


学习内容:

  1. promise链式调用规则。
  2. promise链式调用出现的意义。
  3. 回调地狱及回调地狱解决。
  4. 多个异步函数同步调用实现示例。

学习产出:

promise链式调用规则

上篇讲到,promise对象中三个方法:.then()、.catch().、finally,它们都会返回promise对象。
因此可以实现链式调用。

且调用规则为:

  • then:一般当前promise变为已兑现状态时才会走then,且即使 .then() 缺少返回 Promise 对象的回调函数,处理程序仍会继续到链的下一个链式调用。因此,在最终的 .catch() 之前,可以安全地省略每个链式调用中处理已拒绝状态的回调函数。
  • catch:一般当前promise抛出异常或者调用reject,且没有被前面的then函数处理时(有的异常需要阻断链条就直接在then(null,handleRejected)里处理了)调用。 catch(failureCallback) 等同于 then(null, failureCallback)。
  • finally:一般当前promise变为已兑现状态时会走finally。

有了这层规则,再看多个异步函数同步执行的实现:

  • 假设每个异步函数都改为返回Promise对象形式,且当前异步函数内容执行完后才会修改当前promise的状态。
    -在这里插入图片描述

  • 开始时调用异步函数1,异步函数触发异步调用逻辑、但不会等异步调用完就会返回promise对象。

  • promise对象作为代理承担了异步函数的执行状态管理,即异步函数执行完才会返回成功或者失败,进而走下面的流程。

  • 对上面返回的promise对象调用then方法,在这个then里调用第二个要执行的异步函数。

  • then方法是在上面返回的promise兑现时才去执行的,且返回了一个新的promise。

  • 第二个异步函数又返回的新的promise对象, 再去调用then方法,then方法里调用第三个异步函数…。

  • 上面的then()里,只是(定义)调用了异步函数调用成功的情况,失败的情况,最后放到catch里面进行处理。
    可见promise只是规定了这三个异步函数的调用顺序,对每个异步函数添加了promise代理,在未来特定阶段去控制这些异步函数的触发。由于每个函数都会返回新的promise,从而最终实现了同步的链式调用。

promise链式调用出现的意义

异步函数实现同步调用 ,之前的方式和使用promise两种对比,简单举例:
之前的方式:

/* createAudioFileAsync定义 */
function createAudioFileAsync(audioSettings, successCallback, failureCallback) {
  
  const audioData = generateAudioData(audioSettings); // 模拟生成音频数据的函数
  // writeFile异步调用
  fs.writeFile(audioSettings.path, audioData, (err) => {
    if (err) {
      return failureCallback(err);
    }
    successCallback(`文件已创建:${audioSettings.path}`);
  });
};
// 成功的回调函数
function successCallback(result) {
  console.log("音频文件创建成功:" + result);
}

// 失败的回调函数
function failureCallback(error) {
  console.log("音频文件创建失败:" + error);
}

调用:

createAudioFileAsync(audioSettings, successCallback, failureCallback);

promise形式:
如果设置 createAudioFileAsync() 的返回值为 Promise 对象,则可以把回调函数附加到它的then上面:

function createAudioFileAsync(audioSettings) {
  return new Promise((resolve, reject) => {
    const buffer = createAudioBuffer(audioSettings); // 生成音频缓冲区
    
    // 异步处理音频数据(如编码为 MP3 并保存)
    encodeAudioToMp3(buffer, audioSettings, (encodedData) => {
      saveFile(encodedData, settings.path, (success) => {
        if (success) {
          resolve(`音频文件创建成功:${settings.path}`);
        } else {
          reject(new Error("文件保存失败"));
        }
      });
    }, (encodeErr) => {
      reject(encodeErr);
    });
  });
}

调用:

createAudioFileAsync(audioSettings).then(successCallback,failureCallback);

通过对比可发现,使用Promise使得调用链更加扁平化更加清晰。

在这之前如果需要实现连续执行两个或以上异步方法,上一个异步方法执行完,再去执行下一个,且需要带上上一个的执行结果去执行下一个时,用之前的回调方式写则会出现“回调地狱”。

回调地狱及回调地狱解决

回调地狱
借用mdn网站上的例子,简单讲述下,看不懂也没关系,本来就是很绕的东西,也可以直接跳过看Promise解决的部分。

doSomething(function (result) {
  doSomethingElse(result, function (newResult) {
    doThirdThing(newResult, function (finalResult) {
      console.log(`得到最终结果:${finalResult}`);
    }, failureCallback);
  }, failureCallback);
}, failureCallback);

说明:
有三个异步方法doSomething、doSomethingElse、doThirdThing。
这三个方法相互依赖调用,调用当前方法前依赖上一个的调用结果,于是就出现上述代码。
调用步骤:
(1)先执行异步方法doSomething里的逻辑,比如call后端api获取结果等;
(2)获取到(1)的结果后,执行doSomethingElse函数,这个函数里拿到(1)的结果进行自己的逻辑处理,处理完后返回处理后的结果。
(3)doThirdThing获取到(2)中的结果后,处理doThirdThing里自己的逻辑,包括执行匿名函数里的console.log。
(4)failureCallback贯穿所有异步调用,即无论在哪一步调用异常都能执行到failureCallback去处理异常信息。

这里只简单表述,感兴趣对可访问回调地狱详细链接:http://callbackhell.com/

回调地狱解决(使用Promise) :

doSomething()
.then((result)=> doSomethingElse(result)) // 拿到doSomething返回结果 只有一行代码箭头函数会默认加return 即隐式返回值
.then((newResult)=> doThirdThing(newResult))  // 拿到doSomethingElse返回结果
.then((finalResult)=>{
 console.log(`得到最终结果:${finalResult}`);
 })
.catch(failureCallback)

即doSomething、doSomethingElse、doThirdThing都返回Promise对象,先调用第一个doSomething返回promise,那么then方法里会首先等待这个 promise 的敲定,敲定后then会接收两个回调函数,这里then只处理调用了resolve(已兑现状态)的,拿到resolve中传递的值,再去调用下一个异步函数。
而reject调用后的场景统一在catch中处理(注意:回调函数里会接收到它的兑现值,而不是 Promise 本身)。
对比之前的实现逻辑来看,变成单链条调用是不是简单许多。
当然通过 async/await也可以实现上述情况,且也是扁平易理解,但此处主要说Promise,因此先掠过。

多个异步函数同步调用实现示例

deepSeek获取模型列表 ,并使用其中一个模型进行对话补全。
(此处知只是简单的对话补全,主要是为了巩固promise的使用)。

查看deepseek官方文档发现,可以直接使用openai的sdk去调用dp的api。

dsRobot.js

import OpenAI from 'openai';

const openai = new OpenAI({
  baseURL: 'https://api.deepseek.com/v1',
  apiKey: 'sk-533b5d7ecc6d482dadbb476f6e21bc6f',
  dangerouslyAllowBrowser: true,
});
/* 其中openai.models.list和 openai.chat.completions.create 返回的均是Promise对象 感兴趣可以去扒下源码 */
/* 获取模型列表 官网提供的例子 */
export async function modelList() {
  const models = await openai.models.list();
  for await (const model of models) {
    // 仅仅输出用来查看返回格式
    console.log('=========', model);
  }
  // 这里添加返回 因为后面需要用到
  return models.data;
}
/* 对话补全 官网提供的例子 */
export async function chatCompletions() {
  const completion = await openai.chat.completions.create({
    messages: [{ role: 'system', content: 'You are a helpful assistant.' }],
    model: 'deepseek-chat',
  });
  console.log(completion.choices[0].message.content);
  // 这里添加返回
  return completion.choices[0].message.content;
}

/* 获取模型列表 直接返回promise形式 */
export function modelListForPromise() {
  return openai.models.list().then(models => models.data);
}
/* 对话补全 直接使用promise形式 */
export function chatCompletionsForPromise(param) {
  return openai.chat.completions.create(param)
    .then(completion => completion.choices[0].message.content);
}

可见chatCompletionsForPromise的执行依赖于modelListForPromise的结果,因此调用顺序为:

<template>
   <a-button type="primary" @click="clickCall">
     点击触发
   </a-button>
</template>
<script setup>
  function clickCall() {
      modelListForPromise()
        .then(models => {
          if (models.length === 0) {
            console.error('没有可用的模型');
            return Promise.reject(new Error('没有可用的模型'));
          }
          return { model: models[0].id, messages: [{ role: 'user', content: '你好,世界!' }] };
        })
        .then((param) => {
          console.log('参数:', param);
          return chatCompletionsForPromise(param);
        })
        .then((response) => {
          console.log('聊天响应:', response);
        })
        .catch((error) => {
          console.error('发生错误:', error);
        });
    }
    // 也附加上async/await调用方式
    async function clickCall1() {
      try {
        const models = await modelList();
        console.log('models:', models);
        if (models.length === 0) {
          console.error('没有可用的模型');
          return;
        }
        const response = await chatCompletions(models[0].id);
        console.log('聊天响应:', response);
      }
      catch (error) {
        console.error('发生错误:', error);
      }
    }
 </script>

await 会等待promise兑现后,拿到兑现值。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值