JavaScript is synchronous. This means that it will execute your code block by order after hoisting. Before the code executes, var and function declarations are “hoisted” to the top of their scope.

JavaScript是同步的。 这意味着它将在提升后按顺序执行代码块。 在代码执行之前,将varfunction声明“提升”到其作用域的顶部。

This is an example of a synchronous code:

这是一个同步代码的示例:

console.log('1')console.log('2')console.log('3')

This code will reliably log “1 2 3".

此代码将可靠地记录为“ 1 2 3”。

Asynchronous requests will wait for a timer to finish or a request to respond while the rest of the code continues to execute. Then when the time is right a callback will spring these asynchronous requests into action.

异步请求将等待计时器完成或请求响应,而其余代码将继续执行。 然后,当时间合适时, 回调将使这些异步请求生效。

This is an example of an asynchronous code:

这是一个异步代码的示例:

console.log('1')setTimeout(function afterTwoSeconds() {console.log('2')
}, 2000)console.log('3')

This will actually log “1 3 2”, since the “2” is on a setTimeout which will only execute, by this example, after two seconds. Your application does not hang waiting for the two seconds to finish. Instead it keeps executing the rest of the code and when the timeout is finished it returns to afterTwoSeconds.

实际上,这将记录为“ 1 3 2”,因为“ 2”位于setTimeout上,在此示例中,仅在两秒钟后执行。 您的应用程序不会挂起等待两秒钟。 相反,它将继续执行其余代码,并且当超时完成时,它将返回至afterTwoSeconds。

You may ask “Why is this useful?” or “How do I get my async code to become sync?”. Hopefully I can show you the answers.

您可能会问“为什么这有用?” 或“如何使我的异步代码变得同步?”。 希望我能给你答案。

“问题” (“The problem”)

Let us say our goal is to search for a GitHub user and get all the repositories of that user. The thing is we don’t know the exact name of the user. So we have to list all the users with similar name and their respective repositories.

让我们说我们的目标是搜索GitHub用户并获取该用户的所有存储库。 问题是我们不知道用户的确切名称。 因此,我们必须列出名称相似的所有用户及其各自的存储库。

Doesn’t need to super fancy, something like this

不需要花哨的东西,像这样

In these examples the request code will use XHR (XMLHttpRequest). You can replace it with jQuery $.ajax or the more recent native approach called fetch. Both will give you the promises approach out of the gate.

在这些示例中,请求代码将使用XHR( XMLHttpRequest )。 您可以将其替换为jQuery $.ajax或较新的本地方法fetch 。 两者都会使您脱颖而出。

It will be slightly changed depending on your approach but as a starter:

首先,它会根据您的方法稍有变化:

// url argument can be something like 'https://api.github.com/users/daspinola/repos'function request(url) {const xhr = new XMLHttpRequest();xhr.timeout = 2000;xhr.onreadystatechange = function(e) {if (xhr.readyState === 4) {if (xhr.status === 200) {// Code here for the server answer when successful} else {// Code here for the server answer when not successful}}}xhr.ontimeout = function () {// Well, it took to long do some code here to handle that}xhr.open('get', url, true)xhr.send();
}

Remember that in these examples the important part is not what the end result of the code is. Instead your goal should be to understand the differences of the approaches and how you can leverage them for your development.

请记住,在这些示例中,重要的部分不是代码的最终结果。 相反,您的目标应该是了解方法的差异以及如何利用它们进行开发。

打回来 (Callback)

You can save a reference of a function in a variable when using JavaScript. Then you can use them as arguments of another function to execute later. This is our “callback”.

使用JavaScript时,可以将函数的引用保存在变量中。 然后,您可以将它们用作另一个函数的参数,以便以后执行。 这是我们的“回调”。

One example would be:

一个例子是:

// Execute the function "doThis" with another function as parameter, in this case "andThenThis". doThis will execute whatever code it has and when it finishes it should have "andThenThis" being executed.doThis(andThenThis)// Inside of "doThis" it's referenced as "callback" which is just a variable that is holding the reference to this functionfunction andThenThis() {console.log('and then this')
}// You can name it whatever you want, "callback" is common approachfunction doThis(callback) {console.log('this first')// the '()' is when you are telling your code to execute the function reference else it will just log the referencecallback()
}

Using the callback to solve our problem allows us to do something like this to the request function we defined earlier:

使用callback来解决我们的问题,使我们可以对之前定义的request函数执行以下操作:

function request(url, callback) {const xhr = new XMLHttpRequest();xhr.timeout = 2000;xhr.onreadystatechange = function(e) {if (xhr.readyState === 4) {if (xhr.status === 200) {callback(null, xhr.response)} else {callback(xhr.status, null)}}}xhr.ontimeout = function () {console.log('Timeout')}xhr.open('get', url, true)xhr.send();
}

Our function for the request will now accept a callback so that when a request is made it will be called in case of error and in case of success.

现在,我们用于请求的函数将接受callback以便在发出request时将在错误和成功的情况下调用它。

const userGet = `https://api.github.com/search/users?page=1&q=daspinola&type=Users`request(userGet, function handleUsersList(error, users) {if (error) throw errorconst list = JSON.parse(users).itemslist.forEach(function(user) {request(user.repos_url, function handleReposList(err, repos) {if (err) throw err// Handle the repositories list here})})
})

Breaking this down:

分解如下:

  • We make a request to get a user’s repositories我们请求获取用户的存储库
  • After the request is complete we use callback handleUsersList

    请求完成后,我们使用回调handleUsersList

  • If there is no error then we parse our server response into an object using JSON.parse

    如果没有错误,则使用JSON.parse服务器响应解析为一个对象

  • Then we iterate our user list since it can have more than one

    然后,我们迭代用户列表,因为它可以有多个

    For each user we request their repositories list.

    对于每个用户,我们都请求他们的存储库列表。

    We will use the url that returned per user in our first response

    我们将在第一个响应中使用每个用户返回的网址

    We call

    我们称之为

    repos_urlas the url for our next requests or from the first response

    repos_url作为下一个请求或第一个响应的URL

  • When the request has completed the callback, we will call

    当请求完成回调后,我们将调用

    This will handle either its error or the response with the list of repositories for that user

    这将使用该用户的存储库列表来处理其错误或响应

Note: Sending the error first as parameter is a common practice especially when using Node.js.

注意 :通常首先发送错误作为参数,尤其是在使用Node.js时。

A more “complete” and readable approach would be to have some error handling. We would keep the callback separate from the request execution.

一种更“完整”且易读的方法是要进行一些错误处理。 我们将使回调与请求执行分开。

Something like this:

像这样:

try {request(userGet, handleUsersList)
} catch (e) {console.error('Request boom! ', e)
}function handleUsersList(error, users) {if (error) throw errorconst list = JSON.parse(users).itemslist.forEach(function(user) {request(user.repos_url, handleReposList)})
}function handleReposList(err, repos) {if (err) throw err// Handle the repositories list hereconsole.log('My very few repos', repos)
}

This ends up having problems like racing and error handling issues. Racing happens when you don’t control which user you will get first. We are requesting the information for all of them in case there is more than one. We are not taking an order into account. For example, user 10 can come first and user 2 last. We have a possible solution later in the article.

最终会出现竞速和错误处理问题。 当您无法控制将首先获得哪个用户时,就会发生竞速。 如果不止一个,我们要求提供所有这些信息。 我们没有考虑订单。 例如,用户10可以排在第一位,而用户2可以排在最后。 我们将在本文后面提供一个可能的解决方案。

The main problem with callbacks is that maintenance and readability can become a pain. It sort of already is and the code does hardly anything. This is known as callback hell which can be avoided with our next approach.

回调的主要问题是维护和可读性会变得很痛苦。 它已经存在,并且代码几乎没有任何作用。 这被称为回调地狱 ,可以通过我们的下一种方法来避免。

承诺 (Promises)

Promises you can make your code more readable. A new developer can come to the code base and see a clear order of execution to your code.

保证您可以使代码更具可读性。 新的开发人员可以进入代码库,并查看代码的清晰执行顺序。

To create a promise you can use:

要创建承诺,您可以使用:

const myPromise = new Promise(function(resolve, reject) {// code hereif (codeIsFine) {resolve('fine')} else {reject('error')}})myPromise.then(function whenOk(response) {console.log(response)return response}).catch(function notOk(err) {console.error(err)})

Let us decompose it:

让我们分解一下:

  • A promise is initialized with a function that has resolve and reject statements

    使用具有resolvereject语句的function初始化promise

  • Make your async code inside the Promise function

    使您的异步代码在Promise函数中

    Make your async code inside the Promise functionresolve when everything happens as desired

    当一切都按需进行时,使Promise函数中的异步代码Promise resolve

    Otherwise

    除此以外

    reject

    reject

  • When a resolve is found the .then method will execute for that Promise

    找到resolve.then方法将针对该Promise执行

    When a

    当一个

    reject is found the .catch will be triggered

    发现reject ,将触发.catch

Things to bear in mind:

注意事项:

  • resolve and reject only accept one parameter

    resolvereject仅接受一个参数

    resolve and reject only accept one parameterresolve(‘yey’, ‘works’) will only send ‘yey’ to the .then callback function

    resolvereject只接受一个参数resolve('yey', 'works')将只发送“yey”到.then回调函数

  • If you chain multiple .then

    如果链接多个.then

    Add a

    添加一个

    return if you want the next .then value not to be undefined

    return如果你想下一个.then值不被undefined

  • When a reject is caught with .catch if you have a .then chained to it

    当一个reject被抓到.catch如果你有一个.then链接到它

    It will still execute that

    它仍然会执行

    .then

    .then

    You can see the

    你可以看到

    .then as an “always executes” and you can check an example in this comment

    .then作为“始终执行”,您可以在此注释中查看示例

  • With a chain on .then if an error happens on the first one

    如果链上.then如果第一个发生错误

    It will skip subsequent

    随后将跳过

    .then until it finds a .catch

    .then ,直到找到一个.catch

  • A promise has three states

    一个承诺有三个状态

    A promise has three statespending

    一个承诺有三个待处理状态

  • When waiting for a resolve or reject to happen

    等待resolvereject发生时

    When waiting for a resolve or reject to happenresolved

    当等待resolvereject发生解决时

    When waiting for a resolve or reject to happenresolved rejected

    在等待resolvereject发生时, 解决 被拒绝

  • Once it’s in a resolved or rejected state

    一旦处于已resolved或已rejected状态

    It cannot be changed

    不能改变

Note: You can create promises without the function at the moment of declarations. The way that I’m showing it is only a common way of doing it.

注意 :在声明时,您可以创建没有功能的promise。 我展示它的方式只是这样做的一种常见方式。

“Theory, theory, theory…I’m confused” you may say.

您可能会说:“理论,理论,理论……我很困惑”。

Let’s use our request example with a promise to try to clear things up:

让我们将请求示例与一个承诺一起使用来尝试清除问题:

function request(url) {return new Promise(function (resolve, reject) {const xhr = new XMLHttpRequest();xhr.timeout = 2000;xhr.onreadystatechange = function(e) {if (xhr.readyState === 4) {if (xhr.status === 200) {resolve(xhr.response)} else {reject(xhr.status)}}}xhr.ontimeout = function () {reject('timeout')}xhr.open('get', url, true)xhr.send();})
}

In this scenario when you execute request it will return something like this:

在这种情况下,当您执行request ,它将返回如下内容:

const userGet = `https://api.github.com/search/users?page=1&q=daspinola&type=Users`const myPromise = request(userGet)console.log('will be pending when logged', myPromise)myPromise.then(function handleUsersList(users) {console.log('when resolve is found it comes here with the response, in this case users ', users)const list = JSON.parse(users).itemsreturn Promise.all(list.map(function(user) {return request(user.repos_url)}))}).then(function handleReposList(repos) {console.log('All users repos in an array', repos)}).catch(function handleErrors(error) {console.log('when a reject is executed it will come here ignoring the then statement ', error)})

This is how we solve racing and some of the error handling problems. The code is still a bit convoluted. But its a way to show you that this approach can also create readability problems.

这就是我们解决赛车以及一些错误处理问题的方式。 代码仍然有些复杂。 但是,这是一种向您展示这种方法还会造成可读性问题的方法。

A quick fix would be to separate the callbacks like so:

一个快速的解决方法是像这样将回调分开:

const userGet = `https://api.github.com/search/users?page=1&q=daspinola&type=Users`const userRequest = request(userGet)// Just by reading this part out loud you have a good idea of what the code does
userRequest.then(handleUsersList).then(repoRequest).then(handleReposList).catch(handleErrors)function handleUsersList(users) {return JSON.parse(users).items
}function repoRequest(users) {return Promise.all(users.map(function(user) {return request(user.repos_url)}))
}function handleReposList(repos) {console.log('All users repos in an array', repos)
}function handleErrors(error) {console.error('Something went wrong ', error)
}

By looking at what userRequest is waiting in order with the .then you can get a sense of what we expect of this code block. Everything is more or less separated by responsibility.

通过查看与userRequest等待的userRequest .then您可以了解我们对这个代码块的期望。 一切或多或少都由责任分隔。

This is “scratching the surface” of what Promises are. To have a great insight on how they work I cannot recommend enough this article.

这就是“承诺”的“表面”。 要对它们的工作原理有一个深刻的了解,我不能推荐这篇文章 。

发电机 (Generators)

Another approach is to use the generators. This is a bit more advance so if you are starting out feel free to jump to the next topic.

另一种方法是使用发电机。 这还有一些进步,因此,如果您刚开始,请随时跳到下一个主题。

One use for generators is that they allow you to have async code looking like sync.

生成器的一种用途是,它们使您可以拥有看起来像同步的异步代码。

They are represented by a * in a function and look something like:

它们在函数中由*表示,外观类似于:

function* foo() {yield 1const args = yield 2console.log(args)
}
var fooIterator = foo()console.log(fooIterator.next().value) // will log 1
console.log(fooIterator.next().value) // will log 2fooIterator.next('aParam') // will log the console.log inside the generator 'aParam'

Instead of returning with a return, generators have a yield statement. It stops the function execution until a .next is made for that function iteration. It is similar to .then promise that only executes when resolved comes back.

生成器具有yield语句,而不是返回return 。 它会停止函数执行,直到为该函数迭代创建.next为止。 它类似于.then承诺,仅在解决方案返回时才执行。

Our request function would look like this:

我们的请求函数如下所示:

function request(url) {return function(callback) {const xhr = new XMLHttpRequest();xhr.onreadystatechange = function(e) {if (xhr.readyState === 4) {if (xhr.status === 200) {callback(null, xhr.response)} else {callback(xhr.status, null)}}}xhr.ontimeout = function () {console.log('timeout')}xhr.open('get', url, true)xhr.send()}
}

We want to have the url as an argument. But instead of executing the request out of the gate we want it only when we have a callback to handle the response.

我们希望将url作为参数。 但是,仅当我们具有处理响应的回调时,我们才需要它,而不是从门外执行请求。

Our generator would be something like:

我们的generator将是这样的:

function* list() {const userGet = `https://api.github.com/search/users?page=1&q=daspinola&type=Users`const users = yield request(userGet)yieldfor (let i = 0; i<=users.length; i++) {yield request(users[i].repos_url)}
}

It will:

它会:

  • Wait until the first request is prepared

    等到第一个request准备好

  • Return a function reference expecting a callback for the first request

    返回一个function引用,期望第一个requestcallback

    Our

    我们的

    request function accepts a url

    request函数接受url

    and returns a

    并返回一个

    function that expects a callback

    需要callback function

  • Expect a users to be sent in the next .next

    期望在下一个.next发送users

  • Iterate over users

    遍历users

  • Wait for a .next for each of the users

    等待每个users.next

  • Return their respective callback function返回各自的回调函数

So an execution of this would be:

因此,将执行以下操作:

try {const iterator = list()iterator.next().value(function handleUsersList(err, users) {if (err) throw errconst list = JSON.parse(users).items// send the list of users for the iteratoriterator.next(list)list.forEach(function(user) {iterator.next().value(function userRepos(error, repos) {if (error) throw repos// Handle each individual user repo hereconsole.log(user, JSON.parse(repos))})})})
} catch (e) {console.error(e)
}

We could separate the callback functions like we did previously. You get the deal by now, a takeaway is that we now can handle each individual user repository list individually.

我们可以像以前一样分离回调函数。 您现在就达成了交易,一个收获是,我们现在可以分别处理每个单独的用户存储库列表。

I have mixed felling about generators. On one hand I can get a grasp of what is expected of the code by looking at the generator.

我对发电机一无所知。 一方面,通过查看生成器,我可以了解代码的期望。

But its execution ends up having similar problems to the callback hell.

但是其执行最终会遇到与回调地狱类似的问题。

Like async/await, a compiler is recommended. This is because it isn’t supported in older browser versions.

与async / await一样 ,建议使用编译器。 这是因为较旧的浏览器版本不支持该功能。

Also it isn’t that common in my experience. So it may generate confusing in codebases maintained by various developers.

根据我的经验,这也不是那么普遍。 因此,它可能会在由各种开发人员维护的代码库中引起混乱。

An awesome insight of how generators work can be found in this article. And here is another great resource.

在本文中可以找到有关生成器工作原理的真知灼见。 这是另一个很棒的资源 。

异步/等待 (Async/Await)

This method seems like a mix of generators with promises. You just have to tell your code what functions are to be async. And what part of the code will have to await for that promise to finish.

这种方法似乎是带有承诺的生成器的混合体。 您只需要告诉您的代码哪些函数将是async 。 以及代码的哪一部分必须awaitpromise完成。

sumTwentyAfterTwoSeconds(10).then(result => console.log('after 2 seconds', result))async function sumTwentyAfterTwoSeconds(value) {const remainder = afterTwoSeconds(20)return value + await remainder
}function afterTwoSeconds(value) {return new Promise(resolve => {setTimeout(() => { resolve(value) }, 2000);});
}

In this scenario:

在这种情况下:

  • We have sumTwentyAfterTwoSeconds as being an async function

    我们将sumTwentyAfterTwoSeconds作为异步函数

  • We tell our code to wait for the resolve or reject for our promise function afterTwoSeconds

    afterTwoSeconds我们告诉我们的代码等待我们的promise函数的resolvereject

  • It will only end up in the .then when the await operations finish

    它只会在await操作结束时以.then结尾

    In this case there is only one

    在这种情况下,只有一个

Applying this to our request we leave it as a promise as seen earlier:

将此应用于我们的request我们将其作为一个promise如先前所示:

function request(url) {return new Promise(function(resolve, reject) {const xhr = new XMLHttpRequest();xhr.onreadystatechange = function(e) {if (xhr.readyState === 4) {if (xhr.status === 200) {resolve(xhr.response)} else {reject(xhr.status)}}}xhr.ontimeout = function () {reject('timeout')}xhr.open('get', url, true)xhr.send()})
}

We create our async function with the needed awaits like so:

我们使用所需的等待创建async函数,如下所示:

async function list() {const userGet = `https://api.github.com/search/users?page=1&q=daspinola&type=Users`const users = await request(userGet)const usersList = JSON.parse(users).itemsusersList.forEach(async function (user) {const repos = await request(user.repos_url)handleRepoList(user, repos)})
}function handleRepoList(user, repos) {const userRepos = JSON.parse(repos)// Handle each individual user repo hereconsole.log(user, userRepos)
}

So now we have an async list function that will handle the requests. Another async is needed in the forEach so that we have the list of repos for each user to manipulate.

因此,现在我们有了一个异步list函数来处理请求。 forEach需要另一个异步,以便我们拥有供每个用户操纵的存储repos列表。

We call it as:

我们称其为:

list().catch(e => console.error(e))

This and the promises approach are my favorites since the code is easy to read and change. You can read about async/await more in depth here.

由于易于阅读和更改代码,因此我最喜欢这种方法和Promise方法。 您可以在此处详细了解异步/等待。

A downside of using async/await is that it isn’t supported in the front-end by older browsers or in the back-end. You have to use the Node 8.

使用async / await的缺点是旧版浏览器或后端不支持它的前端。 您必须使用节点8。

You can use a compiler like babel to help solve that.

您可以使用像babel这样的编译器来解决这个问题。

“解” (“Solution”)

You can see the end code accomplishing our initial goal using async/await in this snippet.

您可以在此代码段中看到使用async / await完成我们最初目标的最终代码 。

A good thing to do is to try it yourself in the various forms referenced in this article.

要做的一件好事是以本文引用的各种形式自己尝试。

结论 (Conclusion)

Depending on the scenario you might find yourself using:

根据情况,您可能会发现自己使用:

  • async/await异步/等待
  • callbacks回叫
  • mix混合

It’s up to you what fits your purposes. And what lets you maintain the code so that it is understandable to others and your future self.

取决于您的目的。 什么使您能够维护代码,以便他人和您将来的自己可以理解。

Note: Any of the approaches become slightly less verbose when using the alternatives for requests like $.ajax and fetch.

注意:当对$.ajaxfetch类的请求使用替代方法时,任何一种方法的冗长程度都会略微降低。

Let me know what you would do different and different ways you found to make each approach more readable.

让我知道您会采取什么不同的方式来发现每种方法,以使其更具可读性。

This is Article 11 of 30. It is part of a project for publishing an article at least once a week, from idle thoughts to tutorials. Leave a comment, follow me on Diogo Spínola and then go back to your brilliant project!

这是30条中的第11条。它是一个项目的一部分,该项目每周至少发表一次文章,从无聊的想法到教程。 发表评论,关注我DiogoSpínola ,然后回到您的杰出项目!

翻译自: https://www.freecodecamp.org/news/javascript-from-callbacks-to-async-await-1cc090ddad99/

JavaScript —从回调到异步/等待相关推荐

  1. 第一节:复习委托,并且通过委托的异步调用开启一个新线程和异步回调、异步等待。

    一. 再谈委托 1. 委托是一个关键字为delegate的自定义类型,通过委托可以把方法以参数的形式传递给另外一个方法,实现插件式的开发模式: 同时调用委托的时候,委托所包含的所有方法都会被实现. 2 ...

  2. 第一节:复习委托,并且通过委托的异步调用开启一个新线程和异步回调、异步等待

    一. 再谈委托 1. 委托是一个关键字为delegate的自定义类型,通过委托可以把方法以参数的形式传递给另外一个方法,实现插件式的开发模式: 同时调用委托的时候,委托所包含的所有方法都会被实现. 2 ...

  3. javascript迭代器_JavaScript符号,迭代器,生成器,异步/等待和异步迭代器-全部简单解释...

    javascript迭代器 by rajaraodv 通过rajaraodv JavaScript符号,迭代器,生成器,异步/等待和异步迭代器-全部简单解释 (JavaScript Symbols, ...

  4. 异步回调地狱_如何逃避异步/等待地狱

    异步回调地狱 async/await freed us from callback hell, but people have started abusing it - leading to the ...

  5. javascript的回调函数 同步 异步

    后一个任务等待前一个任务结束再执行.程序执行顺序与任务排列顺序一致的,同步的. 参考: http://www.ruanyifeng.com/blog/2012/12/asynchronous%EF%B ...

  6. JavaScript中异步/等待的用法和理解

    昨天更新的是"JavaScript中的Promise使用详解",其实也就是说了下基本用法和自己对Promise的理解,可能有错误之处,也欢迎指出.今天就说一说"JavaS ...

  7. 如何用JavaScript的回调函数做出承诺

    by Adham El Banhawy 由Adham El Banhawy 如何用JavaScript的回调函数做出承诺 (How to make a Promise out of a Callbac ...

  8. [转]掌握Ajax 第 2 部分: 使用 JavaScript 和 Ajax 发出异步请求 [IBM]

    转自:http://www.ibm.com/developerworks/cn/xml/wa-ajaxintro2/ 掌握 Ajax,第 2 部分: 使用 JavaScript 和 Ajax 发出异步 ...

  9. javascript之回调函数小知识

    Javascript异步编程方法------"回调函数" 这是异步编程最基本的方法. 软件模块之间总是存在着一定的接口,从调用方式上,可以把他们分为三类:同步调用.回调和异步调用. ...

最新文章

  1. PyTorch核心开发者灵魂发问:我们怎么越来越像Julia了?
  2. 负离子发生器模块 ANION GENERATOR
  3. python super()函数(用来调用父类方法)
  4. Ubuntu安装screen
  5. 广东计算机好的2a学校,广东专插本2A院校排名情况
  6. NOIP2019 Emiya家今天的饭
  7. [渝粤教育] 长沙民政职业技术学院 高职公共英语(一) 参考 资料
  8. C# 将PDF转为Excel
  9. 并查集——程序自动分析(洛谷 P1955)
  10. 文件上传时判断文件夹是否存在
  11. 立即从iOS 10和macOS Sierra下载壁纸
  12. 大数据项目实时数据采集流程步骤分析
  13. qq互联android sdk,QQ互联API列表 - YangJunwei
  14. 程序员的而立之年,迷茫之年
  15. 脸型测试软件在线测试,脸型测试软件最新版
  16. Preliminary Design Review(初步设计评审(回顾))
  17. 试用期三个月,快转正的时候,领导说,“你的表现没有达到预期”
  18. 蒙太奇服务器维修,蒙太奇服务器多台互连导片方法.pdf
  19. 免费赠送20个帆布包和2个咖啡杯活动规则必看(会搜索+复制粘贴就行)
  20. 笨方法学python 习题14

热门文章

  1. 大厂offer手到擒来,Java面试真题精选
  2. Codeforces- Educational Codeforces Round 69
  3. dev中文本框等获取焦点事件
  4. Linux第三周作业
  5. go map数据结构
  6. RedHat Enterprise Linux 6 配置Xmanager ,实现图形界面连接
  7. 解决 MyEclipse build workspace 慢,validation javascript 更慢的问题
  8. 怎么这两天总能看到刺激我的好东西
  9. 整理JS+FLASH幻灯片播放图片脚本代码
  10. ROS(Robot Operating System)笔记 : 2.创建并配置package